SQL注入
发生部位
SQL注入安全漏洞发生于应用程序和数据库之间。
原理
当攻击者在输入的字符串之中注入SQL指令,由于程序没有设置相关的字符检查,那么这些指令就会被数据服务器认为是正常的SQL指令并执行,因此,数据服务器就会遭到破坏或者数据被泄露出去。
示例
某个网站的登录验证的SQL查询代码为
strSQL = "SELECT * FROM users WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');"
恶意填入
userName = "1' OR '1'='1";
与
passWord = "1' OR '1'='1";
时,将导致原本的SQL字符串被填为
strSQL = "SELECT * FROM users WHERE (name = '1' OR '1'='1') and (pw = '1' OR '1'='1');"
也就是实际上运行的SQL命令会变成下面这样的
strSQL = "SELECT * FROM users;"
因此达到无账号密码,亦可登录网站。所以SQL注入被俗称为黑客的填空游戏。
SQL注入方式
-
数字型输入
上面的示例就是一种数字型输入。再比如,在浏览器地址栏输入:learn.me/sql/article.php?id=1,这是一个get型接口,发送这个请求相当于调用一个查询语句:
$sql = "SELECT * FROM article WHERE id =",$id
正常情况下,应该返回一个id=1的文章信息。那么,如果在浏览器地址栏输入:learn.me/sql/article.php?id=-1 OR 1 =1,这就是一个SQL注入攻击了,可能会返回所有文章的相关信息。因为1=1一定是true,所以整个where语句永远是true,那么查询的结果相当于整张表的内容。
-
字符型输入
用户登录时调用接口learn.me/sql/login.html,首先连接数据库,然后后台对post请求参数中携带的用户名、密码进行参数校验,即sql的查询过程。假设正确的用户名和密码为user和pwd123,输入正确的用户名和密码、提交,相当于调用了以下的SQL语句:
SELECT * FROM user WHERE username = 'user' ADN password = 'pwd123'
由于用户名和密码都是字符串,SQL注入方法即把参数携带的数据变成mysql中注释的字符串。
(1)’#’:’#'后所有的字符串都会被当成注释来处理
SELECT * FROM user WHERE username = 'user'#'ADN password = '111' SELECT * FROM user WHERE username = 'user'
(2)’-- ’ (–后面有个空格):’-- '后面的字符串都会被当成注释来处理
SELECT * FROM user WHERE username = 'user'-- 'AND password = '111' SELECT * FROM user WHERE username = 'user'
-
搜索型输入
当在搜索框搜索的时候,称为搜索型。搜索型与数字型注入最大的区别在于:数字型不需要百分号闭合,而搜索类型一般要使用百分号来闭合。
如何防御SQL注入
- 在设计应用程序时,完全使用参数化查询(Parameterized Query)来设计资料访问功能。
- 在组合SQL字符串时,先针对所传入的参数加入其他字符(将单引号字符前加上转义字符)。
- 如果使用PHP开发网页程序的话,需加入转义字符之功能(自动将所有的网页传入参数,将单引号字符前加上转义字符)。
- 使用php开发,可写入html特殊函数,可正确阻挡XSS攻击。
- 其他,使用其他更安全的方式连接SQL数据库。例如已修正过SQL注入问题的数据库连接组件,例如ASP.NET的SqlDataSource对象或是 LINQ to SQL。
- 增强WAF的防御力
shell注入(OS命令注入)
发生部位
程序中调用系统命令的地方。
原理
通常情况下,Web应用程序会有需要执行shell命令的时候,有可能只是使用Unix sendmail程序发送电子邮件,或运行指定的Perl和C + +程序。从开发的角度来看,这样做可以减少程序的开发时间。然而,如果攻击者以构造特殊命令字符串的方式,把恶意代码输入一个编辑域(例如缺乏有效验证的输入框),从而传递到后台程序,恶意命令代码一旦执行,就会导致信息泄露或者正常数据的破坏,甚至可能导致恶意命令掌控该用户的电脑和他们的网络。
示例
String imageName = (String) params.get("imageName");
String cmd = "docker pull " + imageName;
Process process = Runtime.getRuntime().exec(cmd);
// ...
在上面的Java代码中,前端传递docker镜像名至后端服务器,由后端服务器调用docker pull
命令拉取对应的镜像文件。当前端传入的imageName
参数值为正常的镜像名称时(如nginx
),会执行docker pull nginx
命令拉取nginx镜像。而当前端传入的imageName
参数值为nginx; echo hacked
,即带有恶意指令echo hacked
时,后端程序将在执行docker pull nginx
成功后执行echo hacked
命令。此处为了演示方便以及避免造成误操作,所以采用了直观且无害的echo
命令。在终端直接运行该命令的结果如下所示:
# 示例中省略了执行结果与本文无关的一些输出信息
$ docker pull nginx; echo hacked
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
hacked # <- 此处为执行恶意命令的结果
shell注入方式
shell注入主要就是利用了一些符号的的特殊作用,下面介绍几种常见的符号。
-
;
连续指令command1; command2
表示执行完command1后继续执行command2,上面的示例就是采取了这种方式。 -
管道
|
command1 | command2
表示先执行command1,然后将command1的输出作为command2的输入,再执行command2。例如:$ ping -c 4 127.0.0.1 | grep loss 4 packets transmitted, 4 packets received, 0.0% packet loss # grep loss会输出ping -c 4 127.0.0.1结果中含loss字段的行
-
&
后台执行command1 & command2
表示在后台执行command1,在前台执行command2。例如:$ ping -c 4 127.0.0.1 & echo hacked [1] 2333 # <- ping命令的进程号 hacked # <- echo hacked的执行结果 PING 127.0.0.1 (127.0.0.1): 56 data bytes # ... ping的执行结果
注:
ping
命令涉及网络I/O,所以输出在echo
命令之后,而非ping
命令在echo
之后执行。 -
&&
与||
command1 && command2
或command1 || command2
二者的区别为
&& command2
中的command2
仅在command1
执行成功后(退出码为0)执行,而|| command2
中的command2
仅在command1
执行失败(退出码为非0值)后执行。 -
command
与$(command)
当命令为
command1 $(command2)
时(command1
command2``同理),command2
的输出将作为command1
的参数。例如用户输入的参数为$(echo hacked)
时,程序在执行ping
之前将会先执行echo hacked
命令。
除了上述几种shell注入的方式以外,例如重定向(>
、>>
、<
、<<
)等也可能被攻击者所利用。
如何防御shell注入
-
避免直接执行用户输入的命令
-
对用户输入的参数进行校验
-
过滤常见的符号
-
使用黑白名单机制
-
使用语言提供的转码方法
-
使用最少权限的用户运行程序
…