Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ jobs:
command_exists ensure_command ensure_repo
prompt_value prompt_yesno press_enter_to_continue
kraken_term_width kraken_clear_screen kraken_print_separator
kraken_valid_target
kraken_display_banner kraken_display_menu kraken_display_config
kraken_initialize_session test_connectivity
kraken_recon_run kraken_scan_run kraken_web_run
Expand Down
10 changes: 10 additions & 0 deletions .shellcheckrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Kraken shellcheck configuration.
# Keep this in sync with SHELLCHECK_OPTS in .github/workflows/ci.yml so
# local linting matches CI.

# SC1091: sourcing dynamic paths (lib/*.sh discovered at runtime).
disable=SC1091

# SC2034: color variables are exported for other modules and may not all
# be used inside a given file.
disable=SC2034
70 changes: 70 additions & 0 deletions docs/IMPROVEMENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Kraken Improvements (v1.2)

This document tracks the improvements applied to Kraken on top of the
v1.1 modular refactor. Each item maps to a single, self-contained
commit so the history stays bisectable.

## Summary

| # | Area | Improvement | Type |
|---|----------|--------------------------------------------------------|--------|
| 1 | logger | Persistent per-session log file (`kraken.log`) | feat |
| 2 | recon | AAAA / TXT / CNAME records, prefer `dig` over `host` | feat |
| 3 | web | Parallel directory enumeration with bounded jobs | perf |
| 4 | report | Markdown report alongside the plaintext report | feat |
| 5 | core | Target validation helper, reused across modules | feat |
| 6 | tooling | `.shellcheckrc` so local lint matches CI | chore |

## Details

### 1. Persistent session log

Before, every `log_step` / `log_info` / `log_warn` / `log_error` /
`log_success` call wrote only to the terminal. A pentest leaves no
trace of what was run and when. `lib/logger.sh` now mirrors every log
line (timestamped, without color codes) into
`${KRAKEN_OUTPUT_DIR}/kraken.log` once a session is initialised. The
file is append-only across resumes of the same session, giving a clean
audit trail.

### 2. Richer reconnaissance

`lib/modules/recon.sh` previously queried only A / MX / NS records via
`host`. It now also collects AAAA (IPv6), TXT (SPF/DKIM/verification)
and CNAME records, and prefers `dig` when present because its output is
easier to parse and more reliable than `host`. `host` remains the
fallback, then `nslookup`, then `getent` - graceful degradation is
preserved.

### 3. Parallel web directory enumeration

`lib/modules/web.sh` probed each candidate path sequentially with a
blocking `curl`. With ~14 paths and a 5s timeout, a dead host could
stall the module for over a minute. Probes now run as bounded
background jobs (default 8 concurrent, override with
`KRAKEN_WEB_JOBS`), results are collected and sorted, then printed and
written deterministically. This matches the project's
parallel-execution convention.

### 4. Markdown report

`lib/modules/report.sh` produced a single plaintext report. It now also
writes a Markdown version (`REPORT_*.md`) with proper headings and
tables, suitable for pasting into tickets, wikis or client
deliverables. The plaintext report is unchanged so existing tooling
keeps working.

### 5. Target validation

A new `kraken_valid_target` helper in `lib/core.sh` rejects empty or
obviously malformed targets (shell metacharacters, whitespace) before
any external tool is invoked. Recon, scan and vuln modules call it
right after prompting, failing fast with a clear message instead of
feeding junk to `nmap` / `curl` / `host`.

### 6. `.shellcheckrc`

The CI workflow disabled SC1091 and SC2034 via `SHELLCHECK_OPTS`, but a
contributor running `shellcheck` locally got different results. A
repo-level `.shellcheckrc` now encodes the same exclusions so local and
CI linting agree.
16 changes: 15 additions & 1 deletion lib/core.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ fi
KRAKEN_CORE_LOADED=1

# Project metadata.
KRAKEN_VERSION="1.1.0"
KRAKEN_VERSION="1.2.0"
KRAKEN_NAME="Kraken Pentest Framework"

# Runtime globals (populated by lib/session.sh).
Expand Down Expand Up @@ -44,6 +44,20 @@ export RESET BOLD DIM
export RED GREEN YELLOW BLUE MAGENTA CYAN
export BRIGHT_RED BRIGHT_GREEN BRIGHT_YELLOW BRIGHT_BLUE BRIGHT_MAGENTA BRIGHT_CYAN

# Validate a target (domain, hostname or IP). Rejects empty input and
# anything containing whitespace or shell metacharacters before it is
# handed to an external tool. Returns 0 when the target looks safe.
kraken_valid_target() {
local target="$1"
[[ -n "${target}" ]] || return 1
# Reject whitespace and shell-dangerous characters.
[[ "${target}" =~ [[:space:]\;\|\&\$\`\(\)\<\>\"\'\\] ]] && return 1
# Must contain only host-legal characters (alnum, dot, hyphen, colon
# for IPv6, and slash so a URL host can be pre-trimmed by the caller).
[[ "${target}" =~ ^[A-Za-z0-9._:/-]+$ ]] || return 1
return 0
}

# Width of the terminal, defaulting to 80 when stdout is not a TTY.
kraken_term_width() {
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1; then
Expand Down
35 changes: 30 additions & 5 deletions lib/logger.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,53 @@ _kraken_timestamp() {
date '+%Y-%m-%d %H:%M:%S'
}

# Plain tag (no color) used for the on-disk log mirror.
_kraken_plain_tag() {
case "$1" in
step) printf '[*]' ;;
info) printf '[i]' ;;
success) printf '[+]' ;;
warn) printf '[!]' ;;
error) printf '[x]' ;;
*) printf '[ ]' ;;
esac
}

# Append a log line to the session log file, if a session is active.
# Color codes are stripped so the file stays grep-friendly.
_kraken_log_to_file() {
local level="$1"
local message="$2"
[[ -n "${KRAKEN_OUTPUT_DIR:-}" && -d "${KRAKEN_OUTPUT_DIR}" ]] || return 0
printf '%s %s - %s\n' \
"$(_kraken_plain_tag "${level}")" "$(_kraken_timestamp)" "${message}" \
>> "${KRAKEN_OUTPUT_DIR}/kraken.log" 2>/dev/null || true
}

_kraken_log() {
local tag="$1"
local message="$2"
local level="${3:-}"
printf '%b %s - %s\n' "${tag}" "$(_kraken_timestamp)" "${message}"
_kraken_log_to_file "${level}" "${message}"
}

log_step() {
_kraken_log "${BRIGHT_MAGENTA}[*]${RESET}" "$1"
_kraken_log "${BRIGHT_MAGENTA}[*]${RESET}" "$1" step
}

log_info() {
_kraken_log "${BRIGHT_BLUE}[i]${RESET}" "$1"
_kraken_log "${BRIGHT_BLUE}[i]${RESET}" "$1" info
}

log_success() {
_kraken_log "${BRIGHT_GREEN}[+]${RESET}" "$1"
_kraken_log "${BRIGHT_GREEN}[+]${RESET}" "$1" success
}

log_warn() {
_kraken_log "${BRIGHT_YELLOW}[!]${RESET}" "$1"
_kraken_log "${BRIGHT_YELLOW}[!]${RESET}" "$1" warn
}

log_error() {
_kraken_log "${BRIGHT_RED}[x]${RESET}" "$1" >&2
_kraken_log "${BRIGHT_RED}[x]${RESET}" "$1" error >&2
}
51 changes: 42 additions & 9 deletions lib/modules/recon.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,50 @@ if [[ -n "${KRAKEN_MODULE_RECON_LOADED:-}" ]]; then
fi
KRAKEN_MODULE_RECON_LOADED=1

# Query a single record type with dig, printing a header and a fallback
# line when nothing is returned.
_kraken_recon_dig_record() {
local target="$1" rtype="$2"
echo "=== ${rtype} Records ==="
local answer
answer=$(dig +short "${rtype}" "${target}" 2>/dev/null)
if [[ -n "${answer}" ]]; then
printf '%s\n' "${answer}"
else
echo "No ${rtype} records found"
fi
echo
}

_kraken_recon_dns_records() {
local target="$1"
local out_file="$2"
{
echo "# DNS Records for ${target} - $(date)"
echo
if command_exists host; then
if command_exists dig; then
local rtype
for rtype in A AAAA MX NS TXT CNAME; do
_kraken_recon_dig_record "${target}" "${rtype}"
done
elif command_exists host; then
echo "=== A Records ==="
host -t A "${target}" 2>/dev/null | grep "has address" || echo "No A records found"
echo
echo "=== AAAA Records ==="
host -t AAAA "${target}" 2>/dev/null | grep "IPv6 address" || echo "No AAAA records"
echo
echo "=== MX Records ==="
host -t MX "${target}" 2>/dev/null | grep "mail is handled" || echo "No MX records"
echo
echo "=== NS Records ==="
host -t NS "${target}" 2>/dev/null | grep "name server" || echo "No NS records"
echo
echo "=== TXT Records ==="
host -t TXT "${target}" 2>/dev/null | grep "descriptive text" || echo "No TXT records"
echo
echo "=== CNAME Records ==="
host -t CNAME "${target}" 2>/dev/null | grep "alias" || echo "No CNAME records"
elif command_exists nslookup; then
echo "=== DNS Info (nslookup) ==="
nslookup "${target}" 2>/dev/null || echo "nslookup failed"
Expand Down Expand Up @@ -64,14 +93,18 @@ _kraken_recon_whois() {
_kraken_recon_reverse_dns() {
local target="$1"
local out_file="$2"
if ! command_exists host; then
return 0
fi
log_step "Attempting reverse DNS..."
local ip
ip=$(host "${target}" 2>/dev/null | grep "has address" | head -1 | awk '{print $NF}')
if [[ -n "${ip}" ]]; then
host "${ip}" > "${out_file}" 2>/dev/null || true
if command_exists dig; then
ip=$(dig +short A "${target}" 2>/dev/null | head -1)
[[ -n "${ip}" ]] && dig +short -x "${ip}" > "${out_file}" 2>/dev/null || true
elif command_exists host; then
ip=$(host "${target}" 2>/dev/null | grep "has address" | head -1 | awk '{print $NF}')
[[ -n "${ip}" ]] && host "${ip}" > "${out_file}" 2>/dev/null || true
else
return 0
fi
if [[ -n "${ip:-}" ]]; then
log_success "Reverse DNS completed"
fi
}
Expand All @@ -84,8 +117,8 @@ kraken_recon_run() {

local target
target=$(prompt_value "Enter target (domain or IP)")
if [[ -z "${target}" ]]; then
log_error "No target specified"
if ! kraken_valid_target "${target}"; then
log_error "Invalid or empty target"
press_enter_to_continue
return
fi
Expand Down
103 changes: 98 additions & 5 deletions lib/modules/report.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ _kraken_report_summary() {
-exec grep -hc "open" {} + 2>/dev/null | awk '{s+=$1} END {print s+0}')
fi
targets=$(find "${KRAKEN_OUTPUT_DIR}" -mindepth 1 -maxdepth 1 -type d 2>/dev/null \
| sed -E 's|.*/(recon|scan|web|vuln)_||' | sort -u | tr '\n' ',' | sed 's/,$//')
| sed -E 's#.*/(recon|scan|web|vuln)_##' | sort -u | tr '\n' ',' | sed 's/,$//')

cat <<EOF
==========================================
Expand Down Expand Up @@ -233,6 +233,93 @@ For authorized security testing only
EOF
}

# Build a Markdown version of the report from the same session data.
_kraken_report_markdown() {
local total_findings=0 hosts_scanned=0 ports_found=0 targets=""

if find "${KRAKEN_OUTPUT_DIR}" -name "findings.txt" -print -quit 2>/dev/null | grep -q .; then
total_findings=$(find "${KRAKEN_OUTPUT_DIR}" -name "findings.txt" \
-exec cat {} + 2>/dev/null | grep -cE '^(MISSING_HEADER|HTTP_METHODS|INFO_DISCLOSURE):' || echo 0)
fi
hosts_scanned=$(find "${KRAKEN_OUTPUT_DIR}" -type d \
\( -name "recon_*" -o -name "scan_*" \) 2>/dev/null | wc -l)
if find "${KRAKEN_OUTPUT_DIR}" -name "nmap_services.txt" -print -quit 2>/dev/null | grep -q .; then
ports_found=$(find "${KRAKEN_OUTPUT_DIR}" -name "nmap_services.txt" \
-exec grep -hc "open" {} + 2>/dev/null | awk '{s+=$1} END {print s+0}')
fi
targets=$(find "${KRAKEN_OUTPUT_DIR}" -mindepth 1 -maxdepth 1 -type d 2>/dev/null \
| sed -E 's#.*/(recon|scan|web|vuln)_##' | sort -u | tr '\n' ',' | sed 's/,$//')

cat <<EOF
# Kraken Pentest Report

- **Generated:** $(date '+%Y-%m-%d %H:%M:%S')
- **Operator:** $(whoami)@$(hostname)
- **Session:** ${KRAKEN_SESSION_NAME}

## Executive summary

| Metric | Value |
|--------|-------|
| Total findings | ${total_findings} |
| Hosts scanned | ${hosts_scanned} |
| Open ports found | ${ports_found} |
| Vulnerabilities | ${total_findings} |

**Scope:** ${targets:-None}
**Output directory:** \`${KRAKEN_OUTPUT_DIR}\`

EOF

_kraken_report_md_section "Reconnaissance" recon_
_kraken_report_md_section "Port scanning" scan_
_kraken_report_md_section "Web enumeration" web_
_kraken_report_md_section "Vulnerability assessment" vuln_

cat <<EOF
## Recommendations

1. Review and patch all identified vulnerabilities, prioritising critical/high severity.
2. Implement missing security headers (X-Frame-Options, Content-Security-Policy, Strict-Transport-Security).
3. Disable unnecessary services and close unused ports.
4. Run periodic penetration tests and continuous monitoring.
5. Keep systems and dependencies patched.

---

_Report generated by ${KRAKEN_NAME} v${KRAKEN_VERSION}. For authorized security testing only._
EOF
}

# Render one Markdown section: a heading plus a fenced dump of each
# matching per-target directory's text files.
_kraken_report_md_section() {
local title="$1" prefix="$2"
echo "## ${title}"
echo
if ! find "${KRAKEN_OUTPUT_DIR}" -maxdepth 1 -type d -name "${prefix}*" -print -quit 2>/dev/null \
| grep -q .; then
echo "_No data found._"
echo
return
fi
local dir target f
for dir in "${KRAKEN_OUTPUT_DIR}"/${prefix}*; do
[[ -d "${dir}" ]] || continue
target=$(basename "${dir}" | sed "s/${prefix}//")
echo "### ${target}"
echo
for f in "${dir}"/*.txt; do
[[ -f "${f}" ]] || continue
echo "**$(basename "${f}")**"
echo '```'
grep -vE '^#|^$' "${f}" 2>/dev/null | head -40
echo '```'
echo
done
done
}

# Entry point for the report generation module.
kraken_report_run() {
kraken_clear_screen
Expand All @@ -245,8 +332,10 @@ kraken_report_run() {
return
fi

local report_file
report_file="${KRAKEN_OUTPUT_DIR}/REPORT_$(date +%Y%m%d_%H%M%S).txt"
local stamp report_file md_file
stamp="$(date +%Y%m%d_%H%M%S)"
report_file="${KRAKEN_OUTPUT_DIR}/REPORT_${stamp}.txt"
md_file="${KRAKEN_OUTPUT_DIR}/REPORT_${stamp}.md"

log_step "Collecting scan data..."
{
Expand All @@ -259,10 +348,14 @@ kraken_report_run() {
_kraken_report_recommendations
} > "${report_file}"

_kraken_report_markdown > "${md_file}"

log_success "Report generated!"
echo
printf '%sReport saved to:%s\n %s%s%s\n\n' \
"${BRIGHT_GREEN}" "${RESET}" "${BRIGHT_BLUE}" "${report_file}" "${RESET}"
printf '%sReports saved to:%s\n %s%s%s\n %s%s%s\n\n' \
"${BRIGHT_GREEN}" "${RESET}" \
"${BRIGHT_BLUE}" "${report_file}" "${RESET}" \
"${BRIGHT_BLUE}" "${md_file}" "${RESET}"

if prompt_yesno "View report now?" "no"; then
echo
Expand Down
Loading
Loading