Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion capabilities/web-security/capability.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema: 1
name: web-security
version: "1.15.0"
version: "1.15.1"
description: >
Web application penetration testing with 83 attack technique playbooks
covering HTTP desync/request smuggling, cache poisoning, SSRF, SSTI, DOM
Expand Down
54 changes: 36 additions & 18 deletions capabilities/web-security/scripts/install_tools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,34 +58,48 @@ py_install() {
fi
}

GO_TOOL_VERSIONS_pdtm="v0.1.5"
GO_TOOL_VERSIONS_protoscope="v0.0.0-20221109213918-8e7a6aafa2c9"
GO_TOOL_VERSIONS_interactsh="v1.3.1"
GO_TOOL_VERSIONS_2fa="v1.2.0"
GO_TOOL_VERSIONS_surf="v0.0.5"
GO_VERSION="1.26.6"
KATANA_VERSION="1.7.0"
KITERUNNER_VERSION="v1.0.2"

have_pd_tool() {
if [ "$1" = "httpx" ]; then
command -v httpx >/dev/null 2>&1 && httpx -version >/dev/null 2>&1 \
|| [ -x "$HOME/.pdtm/go/bin/httpx" ] && "$HOME/.pdtm/go/bin/httpx" -version >/dev/null 2>&1
else
have "$1"
fi
}

PD_TOOLS="nuclei httpx subfinder naabu dnsx uncover alterx tlsx asnmap"
install_pd_tool() {
local tool="$1" package="$2" version="$3"
have_pd_tool "$tool" && return
mkdir -p "$HOME/.pdtm/go/bin"
GOBIN="$HOME/.pdtm/go/bin" go install "${package}@${version}"
}

# What is actually missing, before anything is fetched.
missing_go_tools=""
for tool in protoscope interactsh-client 2fa surf; do
have "$tool" || missing_go_tools="$missing_go_tools $tool"
done
missing_pd_tools=""
for tool in $PD_TOOLS; do
have "$tool" || missing_pd_tools="$missing_pd_tools,$tool"
for tool in nuclei httpx subfinder naabu dnsx uncover alterx tlsx asnmap; do
have_pd_tool "$tool" || missing_pd_tools="$missing_pd_tools $tool"
done
missing_pd_tools="${missing_pd_tools#,}"

# -- Go toolchain (only when something still has to be built) --------------
# Deliberately last in the decision order: the toolchain is a ~150 MB download
# whose only purpose is building the tools above. If they are all present it is
# never needed, so it is never requested.
need_go=false
[ -n "$missing_go_tools" ] && need_go=true
[ -n "$missing_pd_tools" ] && ! have pdtm && need_go=true
[ -n "$missing_pd_tools" ] && need_go=true
if [ "$need_go" = true ] && ! command -v go &>/dev/null; then
GO_VERSION="1.24.3"
case "$ARCH" in
aarch64|arm64) GOARCH="arm64" ;;
*) GOARCH="amd64" ;;
Expand All @@ -94,18 +108,21 @@ if [ "$need_go" = true ] && ! command -v go &>/dev/null; then
export PATH="/usr/local/go/bin:$PATH"
fi

# -- PDTM + ProjectDiscovery tools ----------------------------------------
# -- ProjectDiscovery tools ------------------------------------------------
if [ -n "$missing_pd_tools" ]; then
if ! have pdtm; then
go install "github.com/projectdiscovery/pdtm/cmd/pdtm@${GO_TOOL_VERSIONS_pdtm}"
fi
PDTM_BIN="$(command -v pdtm || echo "$(go env GOPATH)/bin/pdtm")"
"$PDTM_BIN" -install "$missing_pd_tools"
install_pd_tool nuclei github.com/projectdiscovery/nuclei/v3/cmd/nuclei v3.11.1
install_pd_tool httpx github.com/projectdiscovery/httpx/cmd/httpx v1.12.0
install_pd_tool subfinder github.com/projectdiscovery/subfinder/v2/cmd/subfinder v2.16.0
install_pd_tool naabu github.com/projectdiscovery/naabu/v2/cmd/naabu v2.6.1
install_pd_tool dnsx github.com/projectdiscovery/dnsx/cmd/dnsx v1.3.1
install_pd_tool uncover github.com/projectdiscovery/uncover/cmd/uncover v1.2.1
install_pd_tool alterx github.com/projectdiscovery/alterx/cmd/alterx v0.1.0
install_pd_tool tlsx github.com/projectdiscovery/tlsx/cmd/tlsx v1.4.0
install_pd_tool asnmap github.com/projectdiscovery/asnmap/cmd/asnmap v1.1.1
fi

# -- katana (pre-built binary, go-tree-sitter build issue) -----------------
if ! have katana; then
KATANA_VERSION="1.5.0"
DEB_ARCH="$(dpkg --print-architecture 2>/dev/null || echo amd64)"
mkdir -p "$HOME/.pdtm/go/bin"
curl -fsSL "https://github.com/projectdiscovery/katana/releases/download/v${KATANA_VERSION}/katana_${KATANA_VERSION}_linux_${DEB_ARCH}.zip" \
Expand All @@ -132,7 +149,7 @@ have surf || go install "github.com/assetnote/surf/cmd/surf@${GO_TOOL_VERSIONS_s

# -- kiterunner (API content discovery) ------------------------------------
if ! have kr; then
if git clone --depth 1 https://github.com/assetnote/kiterunner /tmp/kiterunner; then
if git clone --depth 1 --branch "$KITERUNNER_VERSION" https://github.com/assetnote/kiterunner /tmp/kiterunner; then
( cd /tmp/kiterunner && make build ) \
&& as_root mv /tmp/kiterunner/dist/kr /usr/local/bin/kr
rm -rf /tmp/kiterunner
Expand Down Expand Up @@ -239,8 +256,9 @@ if ! command -v exiftool &>/dev/null; then
fi

# -- Node.js + agent-browser -----------------------------------------------
if ! command -v node &>/dev/null; then
curl -fsSL https://deb.nodesource.com/setup_22.x | as_root bash - \
NODE_MAJOR="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)"
if [ "$NODE_MAJOR" -lt 24 ]; then
curl -fsSL https://deb.nodesource.com/setup_24.x | as_root bash - \
&& as_root apt-get install -y --no-install-recommends nodejs \
|| echo "WARN: Node.js install failed, skipping"
fi
Expand All @@ -256,7 +274,7 @@ fi
# non-fatal because a disconnected deployment that cannot fetch a browser
# should still get the rest of this capability's tooling.
AGENT_BROWSER_CACHE="${AGENT_BROWSER_CACHE_DIR:-$HOME/.cache/agent-browser}"
if [ ! -d "$AGENT_BROWSER_CACHE" ]; then
if [ "${DREADNODE_CAPABILITY_INSTALL:-}" != "sealed" ] && [ ! -d "$AGENT_BROWSER_CACHE" ]; then
agent-browser install || echo "WARN: agent-browser browser download failed, skipping"
fi

Expand Down
41 changes: 37 additions & 4 deletions capabilities/web-security/tests/test_install_tools_offline.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,32 @@ def test_no_unpinned_go_installs(self) -> None:
]
assert not unpinned, f"unpinned installs: {unpinned}"

def test_projectdiscovery_tools_use_explicit_versions(self) -> None:
pins = {
"nuclei": "v3.11.1",
"httpx": "v1.12.0",
"subfinder": "v2.16.0",
"naabu": "v2.6.1",
"dnsx": "v1.3.1",
"uncover": "v1.2.1",
"alterx": "v0.1.0",
"tlsx": "v1.4.0",
"asnmap": "v1.1.1",
}
for tool, version in pins.items():
assert re.search(
rf"install_pd_tool {tool} \S+ {re.escape(version)}$",
INSTALL_SCRIPT,
re.MULTILINE,
), f"missing {tool} pin {version}"

assert "pdtm -install" not in INSTALL_SCRIPT

def test_toolchain_and_kiterunner_versions_are_pinned(self) -> None:
assert 'GO_VERSION="1.26.6"' in INSTALL_SCRIPT
assert 'KITERUNNER_VERSION="v1.0.2"' in INSTALL_SCRIPT
assert 'git clone --depth 1 --branch "$KITERUNNER_VERSION"' in INSTALL_SCRIPT


class TestFetchesAreGuarded:
def test_every_go_install_is_guarded(self) -> None:
Expand Down Expand Up @@ -94,11 +120,12 @@ def test_py_install_calls_are_guarded(self) -> None:
unguarded.append(stripped)
assert not unguarded, f"unguarded py_install: {unguarded}"

def test_pdtm_only_installs_missing_tools(self) -> None:
# `pdtm -install <full list>` re-fetches every tool in the list. The
# set has to be narrowed to what is actually absent first.
def test_only_missing_projectdiscovery_tools_are_installed(self) -> None:
assert "$missing_pd_tools" in INSTALL_SCRIPT
assert "-install nuclei,httpx" not in INSTALL_SCRIPT
assert 'have_pd_tool "$tool" || missing_pd_tools=' in INSTALL_SCRIPT

def test_httpx_guard_rejects_the_python_cli(self) -> None:
assert "httpx -version >/dev/null 2>&1" in INSTALL_SCRIPT

def test_katana_download_is_guarded(self) -> None:
idx = next(
Expand Down Expand Up @@ -162,6 +189,12 @@ def test_go_cache_cleanup_only_runs_when_go_was_used(self) -> None:
idx = next(i for i, line in enumerate(LINES) if "go clean -cache" in line)
assert "need_go" in _preceding_context(idx, span=3)

def test_node_24_floor_and_sealed_browser_guard(self) -> None:
assert "setup_24.x" in INSTALL_SCRIPT
assert "setup_22.x" not in INSTALL_SCRIPT
assert "${DREADNODE_CAPABILITY_INSTALL:-}" in INSTALL_SCRIPT
assert '!= "sealed"' in INSTALL_SCRIPT


class TestRootEscalation:
"""Writes to root-owned paths (/usr/local/bin, /opt) must use as_root."""
Expand Down
Loading