弱小和无知,不是生存的障碍,傲慢才是。 ——刘慈欣《三体》
注入攻击的本质:把用户输入的数据当作代码执行了
2个关键:
- 用户可以控制输入
- 原本程序要执行的代码拼接了用户输入的数据
有注入漏洞的根本原因:违背了“数据与代码分离”原则
SQL注入
场景:
1 | let Shipcity; |
hacker提交:
1 | Beijing'; drop table OrdersTable-- |
SQL语句执行时变成:
1 | SELECT * FROM OrdersTable WHERE ShipCity = 'Beijing';drop table OrdersTable--' |
查找语句执行后,多了一个删除表的操作。
在SQL注入时,如果网站开启了错误回显,会给hacker带来极大的便利
SQL注入是model层需要解决的问题
盲注(Blind Injection)
- 所谓盲注,就是在服务器没有错误回显的情况下完成注入攻击
- 思路:构造简单的条件语句,通过返回页面的变化,来判断是否存在SQL注入漏洞
场景:一个url为http://newspaper.com/items.php?id=2
的网页,执行SQL语句为
1 | SELECT title, description, body FROM items WHERE ID = 2 |
hacker先构造http://newspaper.com/items.php?id=2 and 1=2
页面无法显示或报错;然后再构造http://newspaper.com/items.php?id=2 and 1=1
发现页面正常显示,说明存在SQL注入的漏洞
Timing Attack
- MySQL中有一个
BENCHMARK(count,expr)
的函数,作用是把expr函数执行count次 - 攻击思路:利用BENCHMAEK()函数,可以让同一个函数执行若干次,使得结果返回的时间比平时长,通过时间长短的变化,可以判断注入语句是否成功了
场景:在http://newspaper.com/items.php?id=2
中,hacker构造id为
1 | 1170 UNION SELECT IF(SUBSTRING(current,1,1) = CHAR(119), BENCHMARK(5000000,ENCODE('MSG', 'by 5 seconds')),null) FROM (Select Database() as current) as tb1; |
这段Payload判断库名的第一个字母是否为CHAR(119)也就是w。如果为真,就会运行BENCHMAEK函数,造成返回时间延长,如果不为真,很快就执行完了。harder遍历所有字母,根据返回时间的长短,判断出数据库的全名。
数据库攻击技巧
常见的攻击技巧
- 例如,访问
http://newspaper.com/items.php?id=2 and substring(@@version,1,1) = 4
,如果mysql的版本是4,就会返回true - 下面的payload分别用了判断表名admin、列名passwd(如果admin表存在)是否存在:
1
2id=2 union all select 1,2,3 from admin
id=2 union all select 1,2,passwd from admin
猜解出用户名和密码
- hacker要提前知道username和password存储在users表中
- 然后通过以下步骤一个字母一个字母地读出来
1 | id=5 and ascii(substring((select concat(username,0x3a,passwd) from users limit 0,1),1,1))>64 /*ret true*/ |
代码解释:
- 0x3a的作用是把username和passwd分开,使得concat之后的效果是username:passwd
- substring函数的下标,是从1开始算的
猜解用户名密码是一个很繁琐的过程,可以用一个自动化工具来帮助你,例如利用sqlmap
利用数据库查看系统本地文件
hacker在sql注入后,还可以查看一些系统本地文件(例如mysql中的LOAD_FILE()可以读取系统文件,INTO DUMPFILE可以写入本地文件),当然这要求当前的数据库用户有读写系统相应文件或目录的权利。
hacker先用语句试探一下数据库用户有没有读取系统文件的权利:
1 | ... union select 1,1 LOAD_FILE('/etc/passwd'),1,1; |
如果可以的话,参考下面代码,hacker可以把系统文件写入一个新的数据库
1 | CREATE TABLE potataes(line BLOB); |
代码解释:
- 先读取系统文件,再写入另一个系统文件,最后导入到表中
- HEX():将一个字符串或数字转换为十六进制格式的字符串
- BLOB:二进制类型
- 除了INTO DUMPFILE还可以用INTO OUTFILE,前者适用于二进制文件,它会把目标文件写入同一行;后者更适用于文本文件
其实,写入文件的技巧,经常被用于导出为一个webshell,为hacker下一步攻击做铺垫
命令执行
- 除了通过导出webshell间接执行命令,hacker还可以利用UDF(用户自定义函数)的技巧来执行命令
- 数据库一般支持从本地导入一个共享库文件,作为自定义文件,使用语法如下:hacker先将lib_mysqludf_sys.so上传到数据库能访问到的路径下,在创建UDF之后就可以使用一些函数执行系统命令了:
1
CREATE FUNCTION f_name RETURN INTEGER SONAME shared_library
- sys_eval():执行任意命令,并将输出返回
- sys_exec():执行任意命令,并将退出码返回
- sys_get():获取一个环境变量
- sys_set():创建或修改一个环境变量
在Oracle数据库中,如果服务器有Java环境,那么也可能造成命令执行。当sql注入后可以执行多语句的情况下,可以在Oracle中创建Java的存储过程执行系统命令。
- 一般来说,如果想在数据库执行系统命令,要求数据库账户有较高的权限
- 因此在建立数据库账户时,应遵循“最小权限原则”
攻击存储过程
- 存储过程就是sql语句的代码封装,类似于自己写的函数
- 存储过程和UDF很像,都为数据库提供了强大的功能,但存储过程必须用CALL或者EXECUTE来执行
- 在MS SQL Server和Oracle数据库中,都有大量的存储过程,为hacker提供了便利
- 例如,在MS SQL Server 2000中,直接可以用下面的方式执行系统命令:
1
2EXEC master.dbo.xp_cmdshell 'cmd.exe dir c: '
EXEC master.dbo.xp_cmdshell 'ping' - 除了利用存储过程直接攻击,存储过程本身也可能存在注入漏洞
编码
例子:
- 在mysql使用GBK编码(是一种宽字符集)时,
0xbf27
是2个字符,其中27是单引号的编码 - 因为注入攻击中常常使用单引号或者双引号,所以执行语句的时候,经常会用“\”来转义
- 如果hacker输入
0xbf27 or 1=1
,那么执行语句的时候0xbf27
会变成0xbf5c27
其中5c
是反斜杠的编码,但在GBK编码中,0xbf5c
是另一个字符,相当于它把转义字符吃掉了,单引号就被保留了下来
解决方案:统一数据库、操作系统、Web应用所使用的字符集
SQL Column Truncation
- mysql中有一个sql_mode选项。当sql_mode设置为default时,即没有开启STRICT_ALL_TABLES选项时,如果用户插入了超长值,mysql只会弹出warning,并且插入成功,而不是弹出error
- 如果hacker注册一个“admin(55个空格)x”的用户,而数据库原本的管理员就叫admin,因为即使超长还是插入成功了,所以hacker成功用新注册的账号登录了admin账户
正确地防御SQL注入
很多开发者认为,执行的时候做一些escape处理就可以了,其实是不够的。
场景:
1 | $sql = "SELECT id,name,mail,cn,blog,twitter FROM register WHERE id = ".mysql_real_escape_string($_GET['id']); |
hacker构造:
1 | http://vuln.example.com/user.php?id=12,AND,1=0,union,select,1,concat(user,0x3a,password),3,4,5,6,from,mysql.user,where,user=substring_index(current_user(),char(64),1) |
代码解释:
- char(64):@
- current_user():是类似
root@localhost
的字符串 - substring_index(string,char,num):对string进行截取操作,截取到char第num次出现之前
之所以hacker会攻击成功,是因为mysql_real_escape_string()只会转义:
- ‘
- “
- /r
- /n
- NULL
- Control-Z
在本例中,都没有用到。其实增加一些过滤字符,也是不靠谱的,因为这种基于黑名单的防御措施,或多或少都会有问题。
不需要空格的例子:
1 | SELECT/**/passwd/**/from/**/user |
不需要引号、括号的例子:
1 | SELECT passwd from users where user=0x61646D696E |
其中0x61646D696E是admin的16进制编码
而且黑名单如果太大,势必会影响正常用户的操作
使用预编译语句
- 防御sql注入最佳方式就是使用预编译语句
- 攻击者无法改变sql的结构,就算hacker插入类似
tom' or '1'='1
之类的语句,也仅仅被当作字符串,当作username来处理
何为预编译语句
通常一个sql语句在被db接收到被返回,经历了以下步骤:
- 词法和语义解析
- 优化sql语句,制定执行计划
- 执行并返回结果
但其实,一些语句只有很微小的差别,但每次都要经过解析、优化,效率就会很低,所以预编译语句应运而生,所谓预编译语句就是将这类语句中的变量用占位符替代,用户只需要填入参数即可
实例:
先创建一个表格
1 | mysql> show create table t\G |
然后通过PREPARE stmt_name FROM preparable_stm
来预编译一条sql语句
1 | mysql> prepare ins from 'insert into t select ?,?'; |
通过EXECUTE stmt_name [USING @var_name [, @var_name] ...]
来执行语句:
1 | mysql> set @a=999,@b='hello'; |
使用存储过程
- 使用存储过程的效果和预编译语句的效果类似,区别在预存储过程需要先将sql语句定义在数据库中
- 存储过程中也是可能存在注入问题的,所以尽量在存储过程中避免使用动态的sql语句,如果无法避免,应该使用严格的输入过滤或者编码函数来处理用户的输入
- 但有时候无法使用预编译语句和存储过程,此时就只能回到输入过滤的编码等方法上
检查数据类型
- 检查数据类型,用以限制用户的输入,在很大程度上可以对抗sql注入
- 但当需要用户提交字符串或者一段短文时,应该用其他方法
使用安全函数
- 各种web语言都实现了一些编码函数,可以对抗sql注入
- 但有一些编码函数是可以被绕过的,所以我们需要一个更加安全的编码函数,可以参考OWASP ESAPI中的实现
- 数据库应该尽量避免web应用以root等高权限身份于自己连接,贯彻最小权限原则
- web应用使用数据库的账户,不应该有创建自定义函数、操作本地文件的权限
不正确的防御姿势
- 例如PHP曾经会用magic_quotes_gpc来防御sql注入,它实际上是调用了一次addslashes(),来将一些特殊符号进行转义。
- 这个例子是在controller层做事情,而sql注入是model需要处理的问题
- 所以会有很多攻击手段绕开这个防御措施
其他注入攻击
XML注入
- XML是一种标记语言,通过标签对数据进行结构化的表示
- 防御措施和html注入很像:对用户输入的数据中,包含的“语言本身的保留字符”进行转义即可
代码注入
- 代码注入与命令注入往往都是由一些不安全的函数或者方法引起的
- 存在代码注入漏洞的地方,与“后门”没有区别
例如PHP中的eval()
:
1 | $myvar = "varname"; |
hacker构造:
1 | /index.php?arg=1; phpinfo() |
PHP中的system($shell, $shell_return)
- $shell: 是shell命令, 如’netstat -tnlp’
- $shell_return: shell命令执行的返回结果,命令执行成功返回0, 否则不为0
- 该函数执行后,直接在终端窗口打印命令执行的结果
- 函数的返回值是命令的执行结果的最后一行
如何防御
- 禁用
eval()
和system()
等可以执行命令的函数 - 如果要使用这些函数,就要对用户的输入数据进行处理
- 在PHP/JSP中避免动态include远程文件,或者安全地处理它
CRLF注入
- CR:
ASXII 13, \r, 0x0d
- LF:
ASXII 10, \n, 0x0a
- 在Linux中
\r\n
表示换行
CRLF注入log
下代码将登陆失败的用户写入log:
1 | def log_failed_login(username) |
正常情况下,会记录:
1 | User login failed for: guest |
hacker写入:
1 | guest\n User login succeeded for: admin |
log变成:
1 | User login failed for: guest |
Http Response Splitting
- Http Response Splitting:HTTP中的CRLF注入
- HTTP头是以\r\n做分隔的
- 可以注入\r\n提前结束HTTP头,然后在HTTP Body中注入恶意HTTP脚本
本文链接: https://bano247.com/2021/11/07/注入攻击/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!