谈escapeshellarg绕过与参数注入漏洞
参数注入漏洞是指,在执行命令的时候,用户控制了命令中的某个参数,并通过一些危险的参数功能,达成攻击的目的。
0x01 从gitlist 0.6.0远程命令执行漏洞说起
我们从gitlist说起,gitlist是一款使用PHP开发的图形化git仓库查看工具。在其0.6.0版本中,存在一处命令参数注入问题,可以导致远程命令执行漏洞。
在用户对仓库中代码进行搜索的时候,gitlist将调用git grep
命令:
<?php
public function searchTree($query, $branch)
{
if (empty($query)) {
return null;
}
$query = escapeshellarg($query);
try {
$results = $this->getClient()->run($this, "grep -i --line-number {$query} $branch");
} catch (RuntimeException $e) {
return false;
}
其中,query是搜索的关键字,branch是搜索的分支。
如果用户输入的$query
的值是--open-files-in-pager=id;
,将可以执行id
命令:
0x02 escapeshellarg为什么没有奏效?
导致这个漏洞的原因,有几点:
- 开发者对于
escapeshellarg
函数的误解,造成参数注入 -
git grep
的参数--open-files-in-pager
的值,将被直接执行
理论上,在经过query = escapeshellarg(query);处理后,
我们可以试一下如下命令:
git grep -i --line-number -e '--open-files-in-pager=id;' master
如上图,我将$query
放在了-e
参数的值的位置,此时它就仅仅是一个字符串而已,并不会被当成参数--open-files-in-pager
。
这应该作为本漏洞的最佳修复方法,也是git官方对pattern可能是用户输入的情况的一种解决方案(以下说明来自man-page):
-e The next parameter is the pattern. This option has to be used for patterns starting with - and should be used in scripts passing user input to grep. Multiple patterns are combined by or.
当然,gitlist的开发者用了另一种修复方案:
<?php
public function searchTree($query, $branch)
{
if (empty($query)) {
return null;
}
$query = preg_replace('/(--?[A-Za-z0-9-]+)/', '', $query);
$query = escapeshellarg($query);
try {
$results = $this->getClient()->run($this, "grep -i --line-number -- {$query} $branch");
} catch (RuntimeException $e) {
return false;
}
首先用preg_replace
将-
开头的非法字符移除,然后将$query
拼接在--
的后面。
在命令行解析器中,--
的意思是,此后的部分不会再包含参数选项(option):
A -- signals the end of options and disables further option processing. Any arguments after the -- are treated as filenames and arguments. An argument of - is equivalent to --. If arguments remain after option processing, and neither the -c nor the -s option has been supplied, the first argument is assumed to be the name of a file containing shell commands. If bash is invoked in this fashion, $0 is set to the name of the file, and the positional parameters are set to the remaining arguments. Bash reads and executes commands from this file, then exits. Bash's exit status is the exit status of the last command executed in the script. If no commands are executed, the exit status is 0. An attempt is first made to open the file in the current directory, and, if no file is found, then the shell searches the directories in PATH for the script.
举个简单的例子,如果我们需要查看一个文件名是--name
的文件,我们就不能用cat --name
来读取,也不能用cat '--name'
,而必须要用cat -- --name
。从这个例子也能看出,单引号并不是区分一个字符串是“参数值”或“选项”的标准。
所以官方这个修复方案也是可以接受的,只不过第一步的preg_replace
有点影响正常搜索功能。
0x03 这不是PHP的专利
熟悉PHP语言的同学一定对PHP执行命令的方法感受深刻,PHP内置的命令执行函数(如shell_exec
、system
),都只接受一个“字符串”作为参数。而在内核中,这个字符串将被直接作为一条shell命令来调用,这种情况下就极为容易出现命令注入漏洞。
由于这个特点,PHP特别准备了两个过滤函数:
- escapeshellcmd
- escapeshellarg
二者分工不同,前者为了防止用户利用shell的一些技巧(如分号、反引号等),执行其他命令;后者是为了防止用户的输入逃逸出“参数值”的位置,变成一个“参数选项”。
但我在0x02中也已经说清楚了,如果开发者在拼接命令的时候,将$query
直接给拼接在“参数选项”的位置上,那用escapeshellarg也就没任何效果了。
Java、Python等语言,执行命令的方法相对来说更加优雅:
import subprocess
query = 'id'
r = subprocess.run(['git', 'grep', '-i', '--line-number', query, 'master'], cwd='/tmp/vulhub')
默认情况下,python的subprocess接受的是一个列表。我们可以将用户输入的query放在列表的一项,这样也就避免了开发者手工转义query的工作,也能从根本上防御命令注入漏洞。但可惜的是,python帮开发者做的操作,也仅仅相当于是PHP中的escapeshellarg。我们可以试试令query等于--open-files-in-pager=id;
:
可见,仍然是存在参数注入漏洞的。原因还是0x02中说的原因,你把query放在了“参数选项”的位置上,无论怎么过滤,或者换成其他语言,都不可能解决问题。
0x04 举一反三
参数注入的例子还比较多,因为大部分的开发者都能理解命令注入的原理,但处理了命令注入后,往往都会忽略参数注入的问题。
最典型是案例是Wordpress PwnScriptum漏洞,PHP mail函数的第五个参数,允许直接注入参数,用户通过注入-X
参数,导致写入任意文件,最终getshell。
另一个典型的例子是php-cgi CVE-2012-1823 ,在cgi模式中,用户传入的querystring将作为cgi的参数传给php-cgi命令。而php-cgi命令可以用-d参数指定配置项,我们通过指定auto_prepend_file=php://input
,最终导致任意代码执行。
客户端上也出现过类似的漏洞,比如Electron CVE-2018-1000006,我们通过注入参数--gpu-launcher=cmd.exe /c start calc
,来让electron内置的chromium执行任意命令。electron的最早给出的缓解措施也是在拼接点前面加上“--”。
- commons-pool与commons-pool2连接池(Hadoop连接池)
- Glusterfs 文件系统
- 时过境迁:Oracle跨平台迁移之XTTS方案与实践
- Kazoo Python Zookeeper 选主
- Linux Redis集群搭建与集群客户端实现
- python使用上下文管理器实现sqlite3事务机制
- python服务端多进程压测工具
- 响应式js幻灯片代码一枚
- 使用深度学习检测面部特征,让实时视频聊天变得更有趣
- springboot 注册服务注册中心(zk)的两种方式
- 极速体验:Oracle 18c 下载和Scalable Sequence新特性
- springboot mybatis 事务管理
- Elasticsearch JAVA api轻松搞定groupBy聚合
- 微信加关注链接 一键关注公众号 解决用手机看网页无法扫二维码的烦恼
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Linux系列之学会使用Top命令进行系统监控
- 一简单线程同步笔试题分享,欢迎纠错分享更多思路
- 卷积神经网络之-NiN 网络(Network In Network)
- 【注意力机制】空间注意力机制之Spatial Transformer Network
- 【小技巧】用Python给你的视频添加字幕
- 震惊!我三步就搞定了 Tomcat 源码环境搭建!
- 【小技巧】深度学习中的那些效率提升利器(附资源)
- 多个线程为了同个资源打起架来了,操作系统是如何让他们安分的?
- 学习链表,这些题你值得一刷!
- Gophish钓鱼测试
- 【位运算】只出现一次的数字 II,数电的知识终于用上了!
- Swaks伪造邮件发件人绕过SPF
- 某次网站的渗透测试
- phpMyAdmin 渗透利用总结
- 【C++简明教程】C++基本语法