2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

生成AIを使った脆弱性調査を自動化するにはどうしたらよいのだろう?

2
Last updated at Posted at 2025-12-10

序章

私は、セキュリティサービスを運営する企業に派遣されている窓際エンジニアです。セキュリティサービスに派遣されているのに、セキュリティ全然わからんのですが、なんとなく生成AIを使って何か作りたくなったので作ってみたシリーズです。

きっかけ

  • 雑誌:SoftwareDesign(技術評論社)に脅威エンジニアリング特集のCI/CDアーキテクチャが紹介されていた。なかなか面白いLLMの活用されているなと思ったのでちょっとチャレンジしてみたいと思った。
  • 最近生成AIを用いたウィルス・攻撃が増えており動作をなんとなく体感しておきたいと思っていた

今から書いていく内容を悪用するようなことは絶対にしないでください。

今から書いていくコードについては生成AIで出力し私がリファクタリングを行ったものになります。

仕様:

開発環境

  • Ubuntu 25
  • CPU:2Core
  • メモリ:2GB
  • Python 3.10以上

動作仕様:

  • サーバのIPアドレスに対して調査コマンドを実行する
  • 結果を生成AIに渡して次に実行すべきコマンドを受け取る
  • 受け取ったコマンドを実行する

詳細動作仕様:

  • 対象サーバのIPアドレスは自分で入力する
  • 対象サーバに対してnmapを実施しその結果をAIに投げて次候補のコマンドを受け取り実行
  • 対話式コマンドの場合は、手動でEnterを連打する
  • 今回の仕様では、結果を再度AIに投げてさらなる調査を行うことはしない
  • 生成AIはGoogle Geminiを前提にコードを書く。ollamaにも対応する

AIを使うポイント

  • プロンプトエンジニアリングをうまくやることでちゃんとコマンド抽出できるようにする
  • AIにはガードレールが存在するのでうまく調査用だけでも聞きだすようにする

事前準備

準備内容の洗い出し

事前作業

1.システムパッケージをインストール(Debian/Ubuntu の例):

sudo apt update
sudo apt install -y nmap python3 python3-pip git

2.Python パッケージをインストール:

pip3 install requests google-genai

3.環境変数の設定(Gemini を使う場合):

export GOOGLE_API_KEY="Gemini_API_KEY"

本作業

コード構成

生成AIを使った脆弱性調査自動かへの一歩
|-- lib            |
|   |-- exe_cmd.py | コマンドを実行するためのもの
|   |-- ext_cmd.py | AIからレスポンスからコマンドを抽出するためのもの
|   |-- gemini.py  | Geminiに対してプロンプトを投げるためのもの
|   |-- nmap.py    | nmapを実行するためのコード(あえて一つだけ分離、趣味です)
|   `-- ollama.py  | ollamaに対してプロンプトを投げるためもの
`-- scan_for_ai.py | メインルーチン用ファイル、lib配下の関数を都度読みこみ

メインルーチン

※なおollama使うときはollamaのところをコメントアウトを解除して、Geminiをコメントアウトしてください。

scan_for_ai.py
import subprocess
import requests
import re
import sys
import lib.gemini as gemini_lib # Gemini 呼び出しモジュール
import lib.nmap as nmap_lib # nmap実行用のモジュール
import lib.ollama as ollama_lib # Ollamaとの通信モジュール
import lib.ext_cmd as ext_cmd_lib # コマンド抽出用モジュール
import lib.exe_cmd as exe_cmd_lib # コマンド実行用モジュール

if __name__ == "__main__":
    target_ip = input("■ 診断対象のIPアドレスを入力してください: ").strip()
    if target_ip:
        print(f"{target_ip } に対してnmapスキャンを実行中...")
        scan_result = nmap_lib.run_nmap(target_ip )
        if not scan_result:
            print("× スキャン結果が取得できませんでした。")
            sys.exit(0)
        else:
            #print("\n■スキャン結果:\n")
            #print(scan_result)

            ## AI解析プロンプト作成
            prompt = f"あなたは脆弱性診断のプロです。以下はnmapのスキャン結果です。調査のために次に行うべきコマンドを箇条書きで列挙し てください。列挙するコマンドはnmap以外でお願いします。コマンドは``で囲ってください。例:`ls test.py`:\n\n{scan_result}"
            
            print(f"■AIに投入しているプロンプト\n {prompt}")
            print("\n■ AIによる解析を実行中...\n")

            ## AIモデル呼び出し部分 --ollama--
            #analysis = ollama_lib.ask_ollama(prompt,"gpt-oss:20b")
            #analysis = ollama_lib.ask_ollama(prompt,"deepseek-r1:8b")

            ## AIモデル呼び出し部分 --gemini--
            analysis = gemini_lib.gemini_text(prompt)
            if not analysis:
                print("× AI解析に失敗しました。")
                sys.exit(0)

            # AI解析結果表示
            print("■ AI解析結果:\n")
            print(analysis)

            # コマンド抽出
            commands = ext_cmd_lib.extract_commands(analysis, limit=20)
            if not commands:
                print("× 実行可能なコマンドが抽出できませんでした。")
                sys.exit(0)

        # 抽出されたコマンドの表示と実行
        print("\n🔧 抽出されたコマンド(最大50件)を順に実行します...")
        exe_cmd_lib.execute_commands(commands)
    else:
        print("× IPアドレスが入力されていません。")

各種ライブラリのコード

● GeminiのAPIを叩くコード

gemini.py
import requests
import os
import time
from google import genai
from google.genai import types
from google.genai.errors import ServerError

def gemini_text(prompt, max_retries=3, wait_seconds=10):
    api_key = os.getenv("GOOGLE_API_KEY")
    client = genai.Client(api_key=api_key)
    for attempt in range(1, max_retries + 1):
        try:
            response = client.models.generate_content(
                model="gemini-2.5-flash",
                contents=[prompt],
                config=types.GenerateContentConfig(
                    max_output_tokens=7000,
                    temperature=0.9,
                    tools=[types.Tool(google_search=types.GoogleSearchRetrieval())]
                )
            )
            return "".join(part.text for part in response.parts)

        except ServerError as e:
            print(f"[Geminiエラー] 試行 {attempt}/{max_retries}{e}")
            if attempt < max_retries:
                print(f"{wait_seconds}秒待機して再試行します...")
                time.sleep(wait_seconds)
            else:
                print("❌ Gemini要約失敗。スキップします。")
                return "[要約失敗]"

● nmapの実行用

nmap.py
import subprocess

def run_nmap(ip):
    try:
        result = subprocess.run(
            ["nmap", "-sV", ip],
            capture_output=True,
            text=True,
            timeout=60
        )
        result.check_returncode()
        return result.stdout
    except subprocess.CalledProcessError as e:
        print(f"⚠️ nmapの実行に失敗しました:\n{e.stderr}")
        return None
    except Exception as e:
        print(f"⚠️ nmap実行中にエラーが発生しました: {e}")
        return None

● 正規表現でコマンドのみを抽出

ext_cmd.py
import re

def extract_commands(text, limit=50):
    """
    AI応答から 'または`で囲まれたコマンドを抽出し、最大 limit 件返す。
    """
    #シングルクォートまたはバッククォートで囲まれたコマンドを抽出
    commands = re.findall(r"[`']([^`']+)[`']", text)
    return commands[:limit]

● 抽出したコマンドを実行し、標準出力する

exe_cmd.py
import subprocess

def execute_commands(commands):
    for i, cmd in enumerate(commands, 1):
        print(f"\n🚀 コマンド {i} 実行中: {cmd}")
        try:
            proc = subprocess.Popen(
                cmd, shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True
            )
            # 標準出力を逐次読み取り
            for line in proc.stdout:
                print(line, end="")  # 改行はlineに含まれるので end="" にする
            # 標準エラーも逐次読み取りしたい場合
            for err in proc.stderr:
                print(f"{err}", end="")
            proc.wait()
        except Exception as e:
            print(f"× 実行中にエラーが発生しました: {e}")

(参考)ollamaで実行する場合、IPは私の家の環境(;'∀')

ollama.py
import requests

def ask_ollama(prompt, model):
    url = "http://192.168.1.205:11434/api/generate"
    payload = {
        "model": model,
        "prompt": prompt,
        "stream": False
    }
    print(f"■ 使用するモデル: {model}")
    print(f"■ Ollama APIに送信するペイロード: {payload}")
    try:
        response = requests.post(url, json=payload, timeout=300)
        response.raise_for_status()
        result = response.json()
        return result["response"]
    except requests.exceptions.RequestException as e:
        print(f"⚠️ Ollamaとの通信に失敗しました: {e}")
        return None
    except Exception as e:
        print(f"⚠️ 応答の処理中にエラーが発生しました: {e}")
        return None

実行結果

さてさて、実際に実行するとどうなるのでしょうか?結果です。(と思ってたらうっかり自宅のollama gpt-oss20Bを指定していましたのであくまで参考までに・・・・(;'∀')

実行結果
root@attacklinux:~/AI_Scan# python3 scan_for_ai.py
■ 診断対象のIPアドレスを入力してください: 192.168.1.206
■ 192.168.1.206 に対してnmapスキャンを実行中...
■AIに投入しているプロンプト
 あなたは脆弱性診断のプロです。以下はnmapのスキャン結果です。調査のために次に行うべきコマンドを箇条書きで列挙してください。列挙するコマンドはnmap以外でお願いします。コマンドは``で囲ってください。例:`ls test.py`:

Starting Nmap 7.95 ( https://nmap.org ) at 2025-11-28 10:00 UTC
Nmap scan report for 192.168.1.206
Host is up (0.000010s latency).
Not shown: 997 closed tcp ports (reset)
PORT     STATE SERVICE    VERSION
22/tcp   open  ssh        OpenSSH 9.9p1 Ubuntu 3ubuntu3.2 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http       Caddy httpd
8080/tcp open  http-proxy granian
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8080-TCP:V=7.95%I=7%D=11/28%Time=692972B6%P=x86_64-pc-linux-gnu%r(G
SF:etRequest,196E,"HTTP/1\.0\x20200\x20OK\r\ncontent-type:\x20text/html;\x
SF:20charset=utf-8\r\ncontent-length:\x206201\r\nserver-timing:\x20total;d
SF:ur=2\.727,\x20render;dur=0\.913\r\nx-content-type-options:\x20nosniff\r
SF:\nx-download-options:\x20noopen\r\nx-robots-tag:\x20noindex,\x20nofollo
SF:w\r\nreferrer-policy:\x20no-referrer\r\nserver:\x20granian\r\ndate:\x20
SF:Fri,\x2028\x20Nov\x202025\x2010:00:22\x20GMT\r\n\r\n<!DOCTYPE\x20html>\
SF:n<html\x20class=\"no-js\x20theme-auto\x20center-alignment-no\"\x20lang=
(--------------------------省略-----------------------------------)
SF:0name=\"description\"\x20content=\"SearXNG\x20\xe2\x80\x94\x20a\x20priv
SF:acy-respecting,\x20open\x20metasearch\x20engine\">\n\x20\x20<meta\x20na
SF:me=\"keywords\"\x20content=\"SearXNG,\x20search,\x20search\x20engine,\x
SF:20metasearch,\x20meta\x20search\">\n\x20\x20<meta\x20name=\"generator\"
SF:\x20content=\"searxng/2025\.10\.30\+9c2b8f2f9\">\n\x20\x20<meta\x20name
SF:=\"referrer\"\x20content=\"no-referrer\">\n\x20\x20<meta\x20name=\"robo
SF:ts\"\x20content=\"noarchive\">\n\x20\x20<meta\x20name=\"viewport\"\x20c
SF:ontent=\"width=device-width,\x20initial-scale=1\">\n\x20\x20<title>Sear
SF:XNG</");
MAC Address: BC:24:11:7C:AB:92 (Proxmox Server Solutions GmbH)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

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


■ AIによる解析を実行中...

■ 使用するモデル: gpt-oss:20b
■ Ollama APIに送信するペイロード: {'model': 'gpt-oss:20b', 'prompt': 'あなたは脆弱性診断のプロです。以下はnmapのスキャン結果です。調査のために次に行うべきコマンドを箇条書きで列挙してください。列挙するコマンドはnmap以外でお願いします。コマンドは``で囲ってください。例:`ls test.py`:\n\nStarting Nmap 7.95 ( https://nmap.org ) at 2025-11-28 10:00 UTC\nNmap scan report for 192.168.1.206\nHost is up (0.000010s latency).\nNot shown: 997 closed tcp ports (reset)\nPORT     STATE SERVICE    VERSION\n22/tcp   open  ssh        OpenSSH 9.9p1 Ubuntu 3ubuntu3.2 (Ubuntu Linux; protocol ()
(------------------------省略----------------------------------------)'
■ AI解析結果:

- `whois 192.168.1.206`
- `curl -I http://192.168.1.206/`
- `curl -I http://192.168.1.206:8080/`
- `curl -X OPTIONS http://192.168.1.206/`
- `curl -X OPTIONS http://192.168.1.206:8080/`
- `ssh -vvv user@192.168.1.206`
- `ssh user@192.168.1.206 "cat /etc/os-release && uname -a"`
- `ssh user@192.168.1.206 "id && last | head"`
- `ssh user@192.168.1.206 "ps aux | grep sshd"`
- `ssh user@192.168.1.206 "dpkg -l | grep -i openssh"`
- `ssh user@192.168.1.206 "dpkg -l | grep -i caddy"`
- `ssh user@192.168.1.206 "dpkg -l | grep -i granian"`
- `ssh user@192.168.1.206 "dpkg -l | grep -i searxng"`
- `gobuster dir -u http://192.168.1.206/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt`
- `gobuster dir -u http://192.168.1.206:8080/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt`
- `dirsearch --url http://192.168.1.206/`
- `dirsearch --url http://192.168.1.206:8080/`
- `nikto -h http://192.168.1.206/`
- `nikto -h http://192.168.1.206:8080/`
- `whatweb http://192.168.1.206/`
- `whatweb http://192.168.1.206:8080/`
- `wfuzz -c -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt --hc 404 http://192.168.1.206/FUZZ`
- `wfuzz -c -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt --hc 404 http://192.168.1.206:8080/FUZZ`
- `sslyze --regular 192.168.1.206`
- `sslscan http://192.168.1.206/`
- `sslscan http://192.168.1.206:8080/`
- `searchsploit "searxng 2025.10.30"`
- `searchsploit "caddy 2"`
- `searchsploit "granian"`
- `curl -s http://192.168.1.206/api/v1/health | jq .`
- `curl -s http://192.168.1.206:8080/api/v1/health | jq .`
- `cat /var/log/auth.log | grep 192.168.1.206` (if ssh access)
- `journalctl -u ssh.service | grep 192.168.1.206` (if ssh access)
- `docker ps -a` (if containerised services)
- `docker inspect $(docker ps -q)` (for container configs)
- `curl -s http://192.168.1.206/.env`
- `curl -s http://192.168.1.206/debug.php`

> *All commands are wrapped in backticks and are intended for further vulnerability enumeration beyond the initial nmap scan.*

🔧 抽出されたコマンド(最大50件)を順に実行します...

🚀 コマンド 1 実行中: whois 192.168.1.206

#
# ARIN WHOIS data and services are subject to the Terms of Use
# available at: https://www.arin.net/resources/registry/whois/tou/
#
# If you see inaccuracies in the results, please report at
# https://www.arin.net/resources/registry/whois/inaccuracy_reporting/
#
# Copyright 1997-2025, American Registry for Internet Numbers, Ltd.
#


NetRange:       192.168.0.0 - 192.168.255.255
CIDR:           192.168.0.0/16
NetName:        PRIVATE-ADDRESS-CBLK-RFC1918-IANA-RESERVED
NetHandle:      NET-192-168-0-0-1
Parent:         NET192 (NET-192-0-0-0-0)
NetType:        IANA Special Use
OriginAS:
Organization:   Internet Assigned Numbers Authority (IANA)
RegDate:        1994-03-15
Updated:        2024-05-24
Comment:        These addresses are in use by many millions of independently operated networks, which might be as small as a single computer connected to a home gateway, and are automatically configured in hundreds of millions of devices.  They are only intended for use within a private context  and traffic that needs to cross the Internet will need to use a different, unique address.
Comment:
Comment:        These addresses can be used by anyone without any need to coordinate with IANA or an Internet registry.  The traffic from these addresses does not come from ICANN or IANA.  We are not the source of activity you may see on logs or in e-mail records.  Please refer to http://www.iana.org/abuse/answers
Comment:
Comment:        These addresses were assigned by the IETF, the organization that develops Internet protocols, in the Best Current Practice document, RFC 1918 which can be found at:
Comment:        http://datatracker.ietf.org/doc/rfc1918
Ref:            https://rdap.arin.net/registry/ip/192.168.0.0



OrgName:        Internet Assigned Numbers Authority
OrgId:          IANA
Address:        12025 Waterfront Drive
Address:        Suite 300
City:           Los Angeles
StateProv:      CA
PostalCode:     90292
Country:        US
RegDate:
Updated:        2024-05-24
Ref:            https://rdap.arin.net/registry/entity/IANA


OrgTechHandle: IANA-IP-ARIN
OrgTechName:   ICANN
OrgTechPhone:  +1-310-301-5820
OrgTechEmail:  abuse@iana.org
OrgTechRef:    https://rdap.arin.net/registry/entity/IANA-IP-ARIN

OrgAbuseHandle: IANA-IP-ARIN
OrgAbuseName:   ICANN
OrgAbusePhone:  +1-310-301-5820
OrgAbuseEmail:  abuse@iana.org
OrgAbuseRef:    https://rdap.arin.net/registry/entity/IANA-IP-ARIN


#
# ARIN WHOIS data and services are subject to the Terms of Use
# available at: https://www.arin.net/resources/registry/whois/tou/
#
# If you see inaccuracies in the results, please report at
# https://www.arin.net/resources/registry/whois/inaccuracy_reporting/
#
# Copyright 1997-2025, American Registry for Internet Numbers, Ltd.
#


🚀 コマンド 2 実行中: curl -I http://192.168.1.206/
HTTP/1.1 200 OK
Server: Caddy
Date: Fri, 28 Nov 2025 10:01:22 GMT

▲   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
▲                                  Dload  Upload   Total   Spent    Left  Speed
▲
▲   0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
▲   0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0

🚀 コマンド 3 実行中: curl -I http://192.168.1.206:8080/
HTTP/1.1 200 OK
content-type: text/html; charset=utf-8
content-length: 6201
server-timing: total;dur=2.208, render;dur=0.82

(-------------省略------------------------)

🚀 コマンド 5 実行中: curl -X OPTIONS http://192.168.1.206:8080/
▲   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
▲                                  Dload  Upload   Total   Spent    Left  Speed
▲
▲   0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
▲   0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0

🚀 コマンド 6 実行中: ssh -vvv user@192.168.1.206
user@192.168.1.206's password:
user@192.168.1.206's password:
user@192.168.1.206's password:
▲ OpenSSH_9.9p1 Ubuntu-3ubuntu3.2, OpenSSL 3.4.1 11 Feb 2025
▲ debug1: Reading configuration data /etc/ssh/ssh_config
▲ debug3: /etc/ssh/ssh_config line 19: Including file /etc/ssh/ssh_config.d/20-systemd-ssh-proxy.conf depth 0
▲ debug1: Reading configuration data /etc/ssh/ssh_config.d/20-systemd-ssh-proxy.conf
▲ debug1: /etc/ssh/ssh_config line 21: Applying options for *
▲ debug2: resolve_canonicalize: hostname 192.168.1.206 is address
▲ debug3: expanded UserKnownHostsFile '~/.ssh/known_hosts' -> '/root/.ssh/known_hosts'
▲ debug3: expanded UserKnownHostsFile '~/.ssh/known_hosts2' -> '/root/.ssh/known_hosts2'
▲ debug3: channel_clear_timeouts: clearing
▲ debug3: ssh_connect_direct: entering
~

所感(感想)

 今回、AIを使うツールを作ってみました。非常に攻撃者が有利だなぁと思いました。というのも、もともとコンピュータは同じことを繰り返すのが得意です。攻撃者はコマンドの成功/失敗に関わらずその結果をもって再トライ継続的に可能です。防御側は色々考えることがあるなと思いました。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?