使用过滤器解决SQL注入和跨站点脚本编制

1 SQL注入、盲注

1.1 SQL注入、盲注概述

Web 应用程序通常在后端使用数据库,以与企业数据仓库交互。查询数据库事实上的标准语言是 SQL(各大数据库供应商都有自己的不同版本)。Web 应用程序通常会获取用户输入(取自 HTTP 请求),将它并入 SQL 查询中,然后发送到后端数据库。接着应用程序便处理查询结果,有时会向用户显示结果。 如果应用程序对用户(攻击者)的输入处理不够小心,攻击者便可以利用这种操作方式。在此情况下,攻击者可以注入恶意的数据,当该数据并入 SQL 查询中时,就将查询的原始语法更改得面目全非。例如,如果应用程序使用用户的输入(如用户名和密码)来查询用户帐户的数据库表,以认证用户,而攻击者能够将恶意数据注入查询的用户名部分(或密码部分),查询便可能更改成完全不同的数据复制查询,可能是修改数据库的查询,或在数据库服务器上运行 Shell 命令的查询。

1.2 安全风险及原因

高风险漏洞,攻击者可能会查看、修改或删除数据库条目和表

原因:未对用户输入正确执行危险字符清理

固定值: 查看危险字符注入的可能解决方案

1.3 AppScan扫描建议

若干问题的补救方法在于对用户输入进行清理。 通过验证用户输入未包含危险字符,便可能防止恶意的用户导致应用程序执行计划外的任务,例如:启动任意 SQL 查询、嵌入将在客户端执行的 Javascript 代码、运行各种操作系统命令,等等。 建议过滤出所有以下字符:

[1] |(竖线符号)

[2] & (& 符号)

[3];(分号)

[4] $(美元符号)

[5] %(百分比符号)

[6] @(at 符号)

[7] '(单引号)

[8] "(引号)

[9] '(反斜杠转义单引号)

[10] "(反斜杠转义引号)

[11] <>(尖括号)

[12] ()(括号)

[13] +(加号)

[14] CR(回车符,ASCII 0x0d)

[15] LF(换行,ASCII 0x0a)

[16] ,(逗号)

[17] (反斜杠)

以下部分描述各种问题、问题的修订建议以及可能触发这些问题的危险字符:

SQL 注入和 SQL 盲注: A. 确保用户输入的值和类型(如 Integer、Date 等)有效,且符合应用程序预期。 B. 利用存储过程,将数据访问抽象化,让用户不直接访问表或视图。当使用存储过程时,请利用 ADO 命令对象来实施它们,以强化变量类型。 C. 清理输入以排除上下文更改符号,例如:

[1] '(单引号)

[2] "(引号)

[3] '(反斜线转义单引号)

[4] "(反斜杠转义引号)

[5] )(结束括号)

[6] ;(分号)

跨站点脚本编制: A. 清理用户输入,并过滤出 JavaScript 代码。我们建议您过滤下列字符:

[1] <>(尖括号)

[2] "(引号)

[3] '(单引号)

[4] %(百分比符号)

[5] ;(分号)

[6] ()(括号)

[7] &(& 符号)

[8] +(加号)

LDAP 注入: A. 使用正面验证。字母数字过滤(A..Z,a..z,0..9)适合大部分 LDAP 查询。 B. 应该过滤出或进行转义的特殊 LDAP 字符:

[1] 在字符串开头的空格或"# “字符

[2] 在字符串结尾的空格字符

[3] ,(逗号)

[4] +(加号)

[5] "(引号)

[6] (反斜杠)

[7] <>(尖括号)

[8] ;(分号)

[9] ()(括号)

ORM 注入: A. 确保用户输入的值和类型(如 Integer、Date 等)有效,且符合应用程序预期。 B. 利用存储过程,将数据访问抽象化,让用户不直接访问表或视图。 C. 使用参数化查询 API D. 清理输入以排除上下文更改符号,例如: (*):

[1] '(单引号)

[2] "(引号)

[3] '(反斜线转义单引号)

[4] "(反斜杠转义引号)

[5] )(结束括号)

[6] ;(分号)

1.4 应用程序解决方案

1、我们为了调试方便,在页面上会抛出数据库异常信息,如果入侵工具获取了这些信息,就可以获取系统的一些配置信息,如web系统框架、采用的数据库等,从而找出系统漏洞。所以不要在页面上抛出异常的详细信息,这些信息对客户并没有用,只是方便技术人员调试罢了,处理方法是在异常处理页面把打印异常代码删除即可;

2、新建一个过滤器,通过过滤器过滤SQL注入特殊字符,配置成功后,重启服务,用Appsan工具扫描,漏洞得到解决。

通过过滤器可以解决SQL注入、跨站点脚本编制及通过框架钓鱼等问题,具体实现方式如下:

1、在web.xml文件中配置过滤器

<!--配置过滤器 通过过滤器过滤SQL注入特殊字符  -->

    <filter>
        <filter-name>InjectFilter</filter-name>
        <filter-class>com.filter.InjectFilter</filter-class>
    </filter>
    <!--映射过滤器-->

    <!--<filter-mapping>
      <filter-name>InjectFilter</filter-name>-->
        <!--"/*"表示拦截所有的请求 -->
       <!-- <url-pattern>/*</url-pattern>
    </filter-mapping>-->

    <filter-mapping>
      <filter-name>InjectFilter</filter-name>
        <!--"/*"表示拦截所有的请求 -->
       <url-pattern>/*</url-pattern>
    </filter-mapping>






**2、过滤器过滤代码**




package com.filter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Enumeration;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.Filter;
import javax.servlet.http.HttpServlet;

/**
*  通过过滤器过滤SQL注入特殊字符  
* */
public  class InjectFilter extends HttpServlet implements Filter {
    /**
        * 
     */
    private static final long serialVersionUID = 5286703103846683570L;
    private String failPage = "/info.jsp";//发生注入时,跳转页面

    public void doFilter(ServletRequest request,ServletResponse response,
            FilterChain filterchain)throws IOException, ServletException {
        //判断是否有注入攻击字符
        HttpServletRequest req = (HttpServletRequest) request;
        String inj = injectInput(req);
        if (!inj.equals("")) {
             request.getRequestDispatcher(failPage).forward(request, response);
             return;
        } else {
            // 传递控制到下一个过滤器
            filterchain.doFilter(request, response);
        }
    }

    /**
        * 判断request中是否含有注入攻击字符
        * @param request
        * @return
     */
    public String injectInput(ServletRequest request) {

        Enumeration e = request.getParameterNames();
        String attributeName;
        String attributeValues[];
        String inj = "";
        String injdb = "";
        while (e.hasMoreElements()) { 
            attributeName = (String)e.nextElement();
            //不对密码信息进行过滤,一般密码中可以包含特殊字符
            if(attributeName.equals("userPassword")||attributeName.equals("confirmPassword")||attributeName.equals("PASSWORD")
                    ||attributeName.equals("password")||attributeName.equals("PASSWORD2")||attributeName.equals("valiPassword")){
                continue;
            }

            attributeValues = request.getParameterValues(attributeName);
            for (int i = 0; i < attributeValues.length; i++) {

    if(attributeValues[i]==null||attributeValues[i].equals(""))
                    continue;
                inj = injectChar(attributeValues[i]);

                if (!inj.equals("")) 
                {
                    return inj;
                }        
            }
        }   
        return inj;
    }

    /**
        * 判断字符串中是否含有注入攻击字符
        * @param str
        * @return
     */
    public String injectChar(String str) {

       String inj_str = "\" ) \' * % < > &";
       String inj_stra[] = inj_str.split(" ");

       for (int i = 0 ; i < inj_stra.length ; i++ )
       {
           if (str.indexOf(inj_stra[i])>=0)
           {
               return inj_stra[i];
           }
       }
       return "";
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub 
           // System.out.println("----注入过滤器初始化----");  
    }  

}




 **3.如果跨站点的脚本编制还存在或者存在其他漏洞风险就再搞个过滤器继续加固验证过滤**


**1.在xml文件继续配置一个新的过滤器:**




<!--配置过滤器 跨站点脚本编制过滤 -->

    <filter>  
        <filter-name>XssFilter</filter-name>  
        <filter-class>com.filter.XssFilter</filter-class>  
    </filter> 
    <!--映射过滤器--> 

    <filter-mapping>
      <filter-name>XssFilter</filter-name>
        <!--"/*"表示拦截所有的请求 -->
        <url-pattern>/*</url-pattern>
    </filter-mapping> 




2.过滤器代码:




package com.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import com.RequestWrapper;
import javax.servlet.http.HttpServlet;

/*
* XSS过滤
 */
public class XssFilter extends HttpServlet implements Filter {

    /**
        * 
     */
    private static final long serialVersionUID = 6846384055249714181L;

    /**  
                * 需要排除的页面  
        */    
        private String excludedPages;    
        private String[] excludedPageArray;   
        @SuppressWarnings("unused")  
        private FilterConfig filterConfig;  
        public void destroy() {  
            this.filterConfig = null;  
           // System.out.println("----过滤器销毁----");
        }  
        public void doFilter(ServletRequest request, ServletResponse response,  
                FilterChain chain) throws IOException, ServletException {  
            boolean isExcludedPage = false;  
         // 强制类型转换 HttpServletRequest
            HttpServletRequest request2 = (HttpServletRequest) request;  
            String ctx_path = request2.getContextPath();  
            String request_url = request2.getRequestURI();  
            String action = request_url.substring(ctx_path.length());  
            HttpServletResponse response2 = (HttpServletResponse)response;
            response2.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");

                if (isExcludedPage) {  
                chain.doFilter(request, response2);  
            } else {  // 构造RequestWrapper对象处理XSS
                chain.doFilter(new RequestWrapper(request2), response2);  
            }  
        }  
        /** 
                * 自定义过滤规则 
         */  
        public void init(FilterConfig filterConfig) throws ServletException {  
            this.filterConfig = filterConfig;  
            excludedPages = filterConfig.getInitParameter("excludedPages");    
            if (StringUtils.isNotEmpty(excludedPages)) {  
                excludedPageArray = excludedPages.replaceAll("[\\s]", "").split(",");  
                //System.out.println("----过滤器初始化----");
            }  
        }  
}




 2.类RequestWrapper




package com.servlet;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import com.util.XssClean;

/*
* 增加XSS过滤
* 自定义的RequestWrapper中,对getParameter和getParameterValues进行重写,从而达到对各个用户输入的form参数的值进行过滤
 */
public class RequestWrapper extends HttpServletRequestWrapper {
    HttpServletRequest orgRequest = null;  
    public RequestWrapper(HttpServletRequest request) {  
        super(request);  
        orgRequest = request;  
    }  
    /** 
        * 获取最原始的request 
        *  
        * @return 
     */ 
    public HttpServletRequest getOrgRequest() {  
        return orgRequest;  
    }  
    /** 
        * 获取最原始的request的静态方法 
        *  
        * @return 
     */ 
    public static HttpServletRequest getOrgRequest(HttpServletRequest req) {  
        if (req instanceof RequestWrapper) {  
            return ((RequestWrapper) req).getOrgRequest();  
        }  
        return req;  
    } 

   /**
        * 覆盖getParameterMap 方法,对sql、html、script注入进行过滤
    */
   public Map<String,String> getParameterMap() {
       HashMap<String,String> paramMap = (HashMap<String,String>) super.getParameterMap();
       paramMap = (HashMap<String,String>) paramMap.clone();

       for (Iterator iterator = paramMap.entrySet().iterator(); iterator.hasNext(); ) {
           Map.Entry<String,String[]> entry = (Map.Entry<String,String[]>) iterator.next();
           String [] values = entry.getValue();
           for (int i = 0; i < values.length; i++) {
               if(values[i] instanceof String){
                   values[i] = XssClean.xssClean(values[i]);
               }
           }
           entry.setValue(values);
       }
       return paramMap;
   }
   //////////////////////////////////////////////////////////////////////////////////////////
   ///////////////////////////////////////////////////////////////////////////////////////////
   //滤掉form data中的恶意脚本
   public String[] getParameterValues(String paramString){  
     String[] arrayOfString1 = super.getParameterValues(paramString);  
     if (arrayOfString1 == null)  
       return null;  
     int i = arrayOfString1.length;  
     String[] arrayOfString2 = new String[i];  
     for (int j = 0; j < i; j++)  
       arrayOfString2[j] =XssClean.xssClean_New(arrayOfString1[j]);  
     return arrayOfString2;  
   }  

   public String getParameter(String paramString){  
     String str = super.getParameter(paramString);  
     if (str == null)  
       return null;  
     return XssClean.xssClean(str);  
   }  
//////////////////////////////////////////////////
   //在输入流中检测滤掉来自其他网站的URL中的恶意脚本
   public String getHeader(String paramString) {  
     String str = super.getHeader(paramString);  
     if (str == null)  
       return null;  
     return XssClean.xssClean(str);  
   } 
   ////////////////////////////////////////////////////////////////////////////////////////////
   ///////////////////////////////////////////////////////////////////////////////////////////////

   public String getQueryString() {
        String value = super.getQueryString();
        if (value != null) {
            value = XssClean.xssClean(value);
        }
        return value;
    }
 }




 3.类XssClean




package com.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.owasp.validator.html.AntiSamy;
import org.owasp.validator.html.CleanResults;
import org.owasp.validator.html.Policy;

import org.owasp.validator.html.PolicyException;
import org.owasp.validator.html.ScanException;
import org.springframework.web.util.HtmlUtils;
import org.apache.commons.io.IOUtils;

/*
* 增加 XSS过滤
* 加入了dependency之后,就可以在xssClean中加入antisamy对恶意脚本进行清理。其中policy.xml是白名单,
* policy.xml中规定了各个html元素所必须满足的条件。antisamy的精髓之处在于,使用policy文件来规定你的过滤条件,
* 若输入字符串不满足policy文件中的条件,则会过滤掉字符中的恶意脚本,返回过滤后的结果。
 */
public class XssClean {
    private static  Policy policy = null;  
    public static Policy getPolicy() throws PolicyException {  
        if (policy == null) {  
            InputStream policyFile = XssClean.class.getResourceAsStream("/resource/antisamy.xml");  //antisamy.xml此文件可能大装载会报错,直接抛出异常即可不用理会也行。

           // 推荐一个jar包,用来转换InputStream到String,
            // 引入apache的io包  import org.apache.commons.io.IOUtils;
            String result = null;
            try {
                result = IOUtils.toString(policyFile, "UTF-8");
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            policy = Policy.getInstance(result); 

        }  
        return policy;  
    }  
     //将client端用户输入的request,在server端进行了拦截,并且进行了过滤
    public static String xssClean_New(String value) { 
        if (StringUtils.isNotEmpty(value)) {  
            AntiSamy antiSamy = new AntiSamy();  
            try {  
                //如果 不用此方法 可以 使用 下面注释的代码实现
                final CleanResults cr = antiSamy.scan(StringUtil.UrlDecode(value, "UTF-8"), getPolicy());  
                // final CleanResults cr = antiSamy.scan(value, Policy.getInstance("antisamy.xml"), AntiSamy.SAX);

                //安全的HTML输出  
                value = cr.getCleanHTML(); 
                value = HtmlUtils.htmlEscape(value);

                //return cr.getCleanHTML();
            } catch (ScanException e) {  
                e.printStackTrace();  
            } catch (PolicyException e) {  
                System.out.println("加载XSS规则文件异常: " + e.getMessage());  
            }  
        }  
        return value;  
    } 


    public static String xssClean_4(String value) {//界面会报错
        value = StringUtil.UrlDecode(value, "UTF-8");
        value = HtmlUtils.htmlEscape(value);
        value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
        value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
        value = value.replaceAll("'", "& #39;");
        value = value.replaceAll("eval\\((.*)\\)", "");
        value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
        value = value.replaceAll("script", "");
        return value;
    } 


    /** 
        * 将容易引起xss漏洞的半角字符直接替换成全角字符 
        *  清除恶意的XSS脚本
        * @param s 
        * @return 
     */  
    public static String xssClean(String value) {  
        if (value == null || value.isEmpty()) {  
            return value;  
        }  
        value = StringUtil.UrlDecode(value, "UTF-8");
        value = HtmlUtils.htmlEscape(value);

        value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
        value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
        value = value.replaceAll("'", "& #39;");
        value = value.replaceAll("eval\\((.*)\\)", "");
        value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
        value = value.replaceAll("script", "");

        StringBuilder sb = new StringBuilder(value.length() + 16);  
        for (int i = 0; i < value.length(); i++) {  
            char c = value.charAt(i);  
            switch (c) {  
            case '>':  
                sb.append(">");// 转义大于号   
                break;  
            case '<':  
                sb.append("<");// 转义小于号   
                break;  
            case '\'':  
                sb.append("'");// 转义单引号   
                break;  
            case '\"':  
                sb.append(""");// 转义双引号   
                break;  
            case '&':  
                sb.append("&");// 转义&   
                break;  
            default:  
                sb.append(c);  
                break;  
            }  
        }  
        return sb.toString();  
    } 


    public static  String xssClean_error(String value) {
        if (value != null) {
            value = value.replaceAll("", "");
            // Avoid anything between script tags
            Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");
            // Avoid anything in a src="http://www.yihaomen.com/article/java/..." type of e­xpression
            scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");
            scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");
            // Remove any lonesome </script> tag
            scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");
            // Remove any lonesome <script ...> tag
            scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");
            // Avoid eval(...) e­xpressions
            scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");
            // Avoid e­xpression(...) e­xpressions
            scriptPattern = Pattern.compile("e­xpression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");
            // Avoid javascript:... e­xpressions
            scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");
            // Avoid vbscript:... e­xpressions
            scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE);
            value = scriptPattern.matcher(value).replaceAll("");
            // Avoid onload= e­xpressions
            scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
            value = scriptPattern.matcher(value).replaceAll("");

            value = HtmlUtils.htmlEscape(value);
        }
        return value;
    }

}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,667评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,361评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,700评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,027评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,988评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,230评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,705评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,366评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,496评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,405评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,453评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,725评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,803评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,015评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,514评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,111评论 2 341

推荐阅读更多精彩内容