- Automatically provisions hundreds of Lambda function workers (each with unique public source IPs) to conduct distributed port scanning and source IP rotation
- Randomizes order of target IP and ports to blend in with generic "noise"
- Automatically tears down all Lambda functions after a scan to prevent a "warm" reload on subseqent scans. This guarantees a fresh, unique source IP for each function worker each time.
Network perimeter appliances and controls are getting much better at detecting scanners and reconnaissance activity. They may be silently blocking or intentionally returning inaccurate results. Normally, the only option is to drastically reduce the scan timing in order to not trigger anti-scanning mechanisms. In circumstances where timing cannot be sacrificed, this tool will likely bypass anti-scanning controls by distributing the scan through hundreds of Lambda function workers, which each having a unique source IP.
- ProxyCannon-NG: Sets up a private VPN with rotation through user-controlled “exit nodes”. CONS: need to provision full EC2/DigitalOcean/etc instances as exit nodes, and those IPs stay permanent per exit node. So to achieve rotation through 10 AWS IPs, you need 10 full EC2 instances.
- Fireprox: Uses AWS API Gateway for creating on the fly HTTP pass-through proxies. CONS: not designed for scanning as each API gateway is a 1-to-1 mapping to a specific target host
- An AWS account
- Setup AWS credentials locally for Boto3 to use (usually either populated in your
~/.aws/credentials
file or environment variables. See Boto3 docs for more details) - Create a IAM Lambda execution role in AWS.
- This will be used for the Lamba function workers.
- This role doesn't need special permisssions, just the
role-trust-policy.json
document
- [optional]: upload the
lambda-port-scanner--ports.zip
to a S3 bucket in your account to take advantage of the--s3-zip
parameter. This allows the controller to generate Lambda functions from this code instead of manually uploading it with each new worker.
There is a one-time use deploy.sh
deploy script included which will do these steps for you:
- Create the required Lambda Execution IAM role
- Create a new S3 bucket and upload the code ZIP files to that bucket
Just run this once and you can then use the resources for the --role
and --s3-zip
arguments:
❯ ./deploy.sh
[*] Creating S3 bucket
"/lambdaportscanner"
[*] Uploading code ZIP files
upload: ./workercode-portscan.zip to s3://lambdaportscanner/workercode-portscan.zip
upload: ./workercode-nmap.zip to s3://lambdaportscanner/workercode-nmap.zip
[*] Creating Lambda IAM Execution Role
** Use these for controller arguments: **
--role "arn:aws:iam::<REDACTED>:role/service-role/lambdaportscanner"
For portscan command: --s3-zip lambdaportscanner/workercode-portscan.zip
For nmap command: --s3-zip lambdaportscanner/workercode-nmap.zip
git clone https://github.com/bridge-four/lambda-port-scanner.git
cd lambda-port-scanner
pipenv install
pipenv run python lambscanController.py --help
git clone https://github.com/bridge-four/lambda-port-scanner.git
cd lambda-port-scanner
pip3 install boto3
python3 lambscanController.py --help
There are 3 subcommands available: portscan
, nmap
, and clean
.
❯ pipenv run python lambscanController.py -h
usage: lambscanController.py [-h] ...
positional arguments:
portscan Portscan subcommand. Get port up/down status. scan is randomized across target ports and IPs
nmap Nmap subcommand. Each Lambda worker will conduct Nmap on its target
clean Clean subcommand. Do not scan. Delete all Lambda functions matching ^scanworker_. Use if something goes wrong.
optional arguments:
-h, --help show this help message and exit
❯ pipenv run python lambscanController.py portscan -h
usage: lambscanController.py portscan [-h] --ports [PORTS] --role [ROLE] [--target [TARGET]] [--target-file [TARGET_FILE]] --region
[REGION] [--workers WORKER_MAX] [--threads THREAD_MAX] [--outfile OUTFILE] [--open]
[--s3-zip [S3ZIP]] [-v]
optional arguments:
-h, --help show this help message and exit
--workers WORKER_MAX Number of Lambda workers to create. Default: 1
--threads THREAD_MAX Max number of threads for port scanning. Default: 1
--outfile OUTFILE Specify the output file to store timestamped scan results
--open Only show open ports
--s3-zip [S3ZIP] Provide a S3 bucket and key path to the code ZIP file and the controller will use that to create each
function worker instead of uploading each manually
-v, --verbose increase console output verbosity
required named arguments:
--ports [PORTS] Ports to scan. Example:80,81,1000-2000
--role [ROLE] AWS IAM role ARN for lambda functions to use
--target [TARGET] IP address or CIDR range
--target-file [TARGET_FILE]
File with one IP address or CIDR per line
--region [REGION] Specify the target region to create and run the lambscan workers (e.g, us-east-1)
Simple example with verbosity (-v
) enabled to see the details of what is happening. This scan provisions 2 Lambda workers to scan 4 ports on the target scanme.nmap.org
. From the output, we can see that those 2 workers were assigned the IPs 3.233.217.229
and 3.233.217.229
from the AWS IP pool, and the controller rotated through them to complete the 4 port scans:
❯ pipenv run python lambscanController.py \
portscan \
--role arn:aws:iam::<>:<> \
--target scanme.nmap.org \
--threads 2 \
--workers 2 \
--ports 21,22,80,443 \
--region us-east-1 \
-v
Resolved scanme.nmap.org to 45.33.32.156
# of Host/Port Combos: 4
[*] Creating 2 Lambda workers in AWS...
Creating worker: scanworker_0
Creating worker: scanworker_1
Success creating: scanworker_1
Success creating: scanworker_0
Verifying worker count...
Workers: 2
[*] Scanning ports...
Using 2 threads
Using scanworker_0 for target: 45.33.32.156:443
Using scanworker_1 for target: 45.33.32.156:21
Using scanworker_0 for target: 45.33.32.156:80
3.237.37.244 --> 45.33.32.156 443/tcp Closed/Filtered
Using scanworker_1 for target: 45.33.32.156:22
34.231.122.36 --> 45.33.32.156 21/tcp Closed/Filtered
3.237.37.244 --> 45.33.32.156 80/tcp Open
34.231.122.36 --> 45.33.32.156 22/tcp Open
All threads completed
[*] Deleting Lambda workers from AWS...
Lambda Functions to delete: 2
Deleting scanworker_1
Deleting scanworker_0
('Success Deleting', 'scanworker_1')
('Success Deleting', 'scanworker_0')
Lambda Functions now: 0
[*] Done!
Scan targeted ports on a target subnet and only show open ports. (This rotates each port scan through 20 Lambda workers provisioned in the us-east-1
region, which will provide 20 unique source IPs to rotate through):
❯ pipenv run python lambscanController.py \
portscan \
--role arn:aws:iam::<>:<> \
--target 52.94.76.0/24 \
--threads 20 \
--workers 20 \
--ports 21,22,80,443,8080 \
--region us-east-1 \
--open
[*] Creating 20 Lambda workers in AWS...
[*] Scanning ports...
54.237.193.133 --> 52.94.76.149 22/tcp Open
3.83.78.214 --> 52.94.76.252 443/tcp Open
All threads completed
[*] Deleting Lambda workers from AWS...
[*] Done!
Scan ports 1-1024 on all targets in the targets.txt
file, using 800 Lambda function workers in the us-west-1
region. Lambda functions will be created using the code uploaded to s3://lambdaportscanner/workercode-portscan.zip
using the deploy script. All requests (both open and closed) will show on the console and will also be logged with a timestamp to scan.log
:
❯ pipenv run python lambscanController.py \
portscan \
--role arn:aws:iam::<>:<> \
--s3-zip lambdaportscanner/workercode-portscan.zip
--target-file targets.txt \
--threads 200 \
--workers 800 \
--ports 1-1024 \
--region us-west-1 \
--outfile scan.log
[*] Creating 800 Lambda workers in AWS...
...<snip>...
❯ pipenv run python lambscanController.py nmap -h
usage: lambscanController.py nmap [-h] [--target [TARGET]] [--target-file [TARGET_FILE]] --region [REGION] --role [ROLE]
[--workers WORKER_MAX] [--threads THREAD_MAX] [--s3-zip [S3ZIP]] [--outfile OUTFILE] [-v]
nmapargs
positional arguments:
nmapargs Arguments to pass to nmap. Note: do NOT include any output flags. Add -- before all nmap arguments to parse
correctly. Example: lambscanController.py nmap --target [] --role [] --region [] --s3-zip [] -- "-Pn --top-
ports 100"
optional arguments:
-h, --help show this help message and exit
--workers WORKER_MAX Number of Lambda workers to create. Default: 1
--threads THREAD_MAX Max number of threads for port scanning. Default: 1
--s3-zip [S3ZIP] Provide a S3 bucket and key path to the code ZIP file and the controller will use that to create each
function worker instead of uploading each manually
--outfile OUTFILE Specify the output file to store timestamped scan results
-v, --verbose increase console output verbosity
required named arguments:
--target [TARGET] IP address or CIDR range
--target-file [TARGET_FILE]
File with one IP address or CIDR per line
--region [REGION] Specify the target region to create and run the lambscan workers (e.g, us-east-1)
--role [ROLE] AWS IAM role ARN for lambda functions to use
Note: the nmap command automatically grabs the .nmap
, .gnmap
, and .xml
output files for each scan from the Lambda worker and writes it to the current directory.
Example nmap against the same targets in the targets.txt file. We pass the Nmap arguments -Pn -vv --reason --top-ports 50
.
IMPORTANT: ONLY TCP CONNECT Nmap scans are allowed, see "Caveats" section below for details.
❯ pipenv run python lambscanController.py \
nmap \
--role "arn:aws:iam::372924254905:role/service-role/lambda-port-scanner"
--s3-zip lambdaportscanner/workercode-nmap.zip
--target-file targets.txt
--threads 10
--workers 10
--region us-east-1
-- "-Pn -vv --reason --top-ports 50"
[*] Creating 10 Lambda workers in AWS...
[*] Executing Nmap scans...
52.91.251.144 --> 45.33.32.156
3.238.204.3 --> 122.51.154.13
3.91.89.108 --> 38.153.23.30
3.237.190.89 --> 106.4.192.53
3.236.105.223 --> 82.17.26.186
34.239.128.242 --> 121.115.226.105
34.232.63.54 --> 141.36.60.117
All threads completed
[*] Deleting LambScan functions from AWS...
And then we can see the raw nmap output returned from the Lambda worker for the scan of the 45.33.32.156
target at 45.33.32.156.nmap
❯ cat 45.33.32.156.nmap
# Nmap 7.60 scan initiated Fri Feb 12 18:53:10 2021 as: ./nmap -Pn -vv --reason --top-ports 50 -oA /tmp/45.33.32.156 45.33.32.156
Nmap scan report for scanme.nmap.org (45.33.32.156)
Host is up, received user-set (0.075s latency).
Scanned at 2021-02-12 18:53:10 UTC for 1s
PORT STATE SERVICE REASON
21/tcp closed ftp conn-refused
22/tcp open ssh syn-ack
23/tcp closed telnet conn-refused
25/tcp filtered smtp no-response
26/tcp closed rsftp conn-refused
53/tcp closed domain conn-refused
80/tcp open http syn-ack
81/tcp closed hosts2-ns conn-refused
110/tcp closed pop3 conn-refused
...<snip>...
Crash, exception, or other issues? Trigger a manual cleanup of any leftover worker functions that may still exist in a region:
pipenv run python lambscanController.py clean --region us-east-1
- IMPORTANT: The
nmap
subcommand CANNOT run with root privileges on the Lambda workers. This means you will get errors when attempting flags such as SYN-scan-sS
, UDP-pU
, fragmentation-f
, and others. - You may encounter stability issues when using more than ~800 function workers, such as rate limiting issues (
TooManyRequestsException
), concurrency issues, or Boto3 session issues. If this occurs, reduce the worker count or play with the settings of theself.botoConfig
Boto3 configuration of the Scanner class.
Some functionality inspired from or originally developed in LambScan and Nmap-aws. Grateful to these developers for their contributions to open source!
- Concurrency performance comparisons with asyncio or aioboto3 (or potentially shift the execution to an AWS Batch service -- this would be much more scalable)
- Add ability to do a full Nmap per target instead of simple port up/down checks
- The 1000 function limit is technically per region, so we could technically also add ability to distribute the scan across multiple regions to go above that limit
- Maybe add the option to store output/logs in a S3 bucket or DynamoDB