From e356252a4b4c331c83a571eeae476640905b078b Mon Sep 17 00:00:00 2001 From: Andrew Aiken Date: Sun, 4 Aug 2024 16:03:32 -0400 Subject: [PATCH] Update SSRF scenario, webserver and terraform code --- .gitignore | 1 + scenarios/ec2_ssrf/assets/ssrf_app/README.md | 24 +- scenarios/ec2_ssrf/assets/ssrf_app/app.js | 58 ++++ scenarios/ec2_ssrf/assets/ssrf_app/app.zip | Bin 2582 -> 0 bytes scenarios/ec2_ssrf/assets/ssrf_app/install.sh | 14 - .../ec2_ssrf/assets/ssrf_app/package.json | 23 ++ .../ec2_ssrf/assets/ssrf_app/ssrf-demo-app.js | 69 ----- scenarios/ec2_ssrf/terraform/data.tf | 31 +++ scenarios/ec2_ssrf/terraform/data_sources.tf | 4 - scenarios/ec2_ssrf/terraform/ec2.tf | 250 ++++++++---------- scenarios/ec2_ssrf/terraform/iam.tf | 173 ++++++------ scenarios/ec2_ssrf/terraform/lambda.tf | 73 +++-- scenarios/ec2_ssrf/terraform/null_resource.tf | 11 - scenarios/ec2_ssrf/terraform/outputs.tf | 11 +- scenarios/ec2_ssrf/terraform/provider.tf | 28 +- scenarios/ec2_ssrf/terraform/s3.tf | 36 +-- scenarios/ec2_ssrf/terraform/variables.tf | 46 ++-- scenarios/ec2_ssrf/terraform/vpc.tf | 62 ++--- 18 files changed, 448 insertions(+), 466 deletions(-) create mode 100644 scenarios/ec2_ssrf/assets/ssrf_app/app.js delete mode 100644 scenarios/ec2_ssrf/assets/ssrf_app/app.zip delete mode 100755 scenarios/ec2_ssrf/assets/ssrf_app/install.sh create mode 100644 scenarios/ec2_ssrf/assets/ssrf_app/package.json delete mode 100644 scenarios/ec2_ssrf/assets/ssrf_app/ssrf-demo-app.js create mode 100644 scenarios/ec2_ssrf/terraform/data.tf delete mode 100644 scenarios/ec2_ssrf/terraform/data_sources.tf delete mode 100644 scenarios/ec2_ssrf/terraform/null_resource.tf diff --git a/.gitignore b/.gitignore index bb634c07..1daea1a5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ __pycache__ cloudgoat cloudgoat.pub output.txt +.DS_Store # Configuration files: config.yml diff --git a/scenarios/ec2_ssrf/assets/ssrf_app/README.md b/scenarios/ec2_ssrf/assets/ssrf_app/README.md index b3c24771..5bdf0204 100644 --- a/scenarios/ec2_ssrf/assets/ssrf_app/README.md +++ b/scenarios/ec2_ssrf/assets/ssrf_app/README.md @@ -5,12 +5,7 @@ This app was adopted from https://github.com/sethsec/Nodejs-SSRF-App.git with lo # Nodejs-SSRF-App Nodejs application intentionally vulnerable to SSRF -#Operating Systems -Ubuntu 14.04 TLS - -Kali 2.0 - -#Download and Setup +## Download and Setup ```ShellSession seth@ubuntu:/opt# sudo git clone https://github.com/sethsec/Nodejs-SSRF-App.git @@ -32,3 +27,20 @@ seth@ubuntu:/opt/Nodejs-SSRF-App# sudo nodejs ssrf-demo-app.js ################################################## ``` + +## Build and run in a Docker container + +```ShellSession +❯ git clone git@github.com:sethsec/Nodejs-SSRF-App.git +❯ cd Nodejs-SSRF-App/ +❯ docker build -t "nodejs-ssrf-app" . +❯ docker run -it -p 8000:8000 nodejs-ssrf-app:latest + +################################################## +# +# Server listening for connections on port:8000 +# Connect to server using the following url: +# -- http://[server]:8000/?url=[SSRF URL] +# +################################################## +``` diff --git a/scenarios/ec2_ssrf/assets/ssrf_app/app.js b/scenarios/ec2_ssrf/assets/ssrf_app/app.js new file mode 100644 index 00000000..56c73310 --- /dev/null +++ b/scenarios/ec2_ssrf/assets/ssrf_app/app.js @@ -0,0 +1,58 @@ +////////////////////////////////////////// +// SSRF Demo App +// Node.js Application Vulnerable to SSRF +// Written by Seth Art +// MIT Licensed +////////////////////////////////////////// + +var needle = require('needle'); +var express = require('express'); + +// Currently this app is also vulnerable to reflective XSS as well. Kind of an easter egg :) + +var app = express(); +var port = 80 + +app.get('/', function (request, response) { + var url = request.query['url']; + if (request.query['mime'] == 'plain') { + var mime = 'plain'; + } else { + var mime = 'html'; + }; + + console.log('New request: ' + request.url); + + // If the URL is not set, then we will just return the default page. + if (url == undefined) { + response.writeHead(200, { 'Content-Type': 'text/' + mime }); + response.write('

Welcome to sethsec\'s SSRF demo.

\n\n'); + response.write('

I am an application. I want to be useful, so give me a URL to requested for you\n



\n\n\n'); + response.end(); + } else { // If the URL is set, then we will try to request it. + needle.get(url, { timeout: 3000 }, function (error, response1) { + // If the request is successful, then we will return the response to the user. + if (!error && response1.statusCode == 200) { + response.writeHead(200, { 'Content-Type': 'text/' + mime }); + response.write('

Welcome to sethsec\'s SSRF demo.

\n\n'); + response.write('

I am an application. I want to be useful, so I requested: ' + url + ' for you\n



\n\n\n'); + console.log(response1.body); + response.write(response1.body); + response.end(); + } else { // If the request is not successful, then we will return an error to the user. + response.writeHead(404, { 'Content-Type': 'text/' + mime }); + response.write('

Welcome to sethsec\'s SSRF demo.

\n\n'); + response.write('

I wanted to be useful, but I could not find: ' + url + ' for you\n



\n\n\n'); + response.end(); + console.log(error) + } + }); + } +}) + +app.listen(port); + +console.log('\n##################################################') +console.log('#\n# Server listening for connections on port:' + port); +console.log('# Connect to server using the following url: \n# -- http://[server]:' + port + '/?url=[SSRF URL]') +console.log('#\n##################################################') diff --git a/scenarios/ec2_ssrf/assets/ssrf_app/app.zip b/scenarios/ec2_ssrf/assets/ssrf_app/app.zip deleted file mode 100644 index 5bb5da1005adf3aa3837ecadccc746144e60a73d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2582 zcmZ{mdpy(MAICqrZQ6`(m`hWcOEM~LN#t4>Yvz*Z;!ezcE@5eCs8I^Jr;t%DjZ`e6 zFB66oYN8dsDVn>a@Ll!m_p5%t-#LH09_Re=Jdg7}@AvDx@mN8jjQ{|Efv|*r+|%ih z=m!4Ec?|%pQ z{v#B;k~iuuvzC(?oXl${*7Js=EIGZl#{-`s?_cXb8D+djhN@R%U-T$nx^iiW6k_61 z@BY|M06u(X3N$ALxuYU$k~$qY3)QE8E>6E|4AFj)n|b!r<)hdbZ75AC$u*OEp(8Ko zRzENHYhoHD$&)wpaOWaU#TaeR!guO)PWmkw-rF*me(Uc8h;WNM<015tqKJk)DjkAh z>j>a~yQZ3?J~B=K0L+B|0K~thg_)5F&f3JCfW0uFDN!%`6+z7jZ6qSPvHs?_iN^Bn zt!+28_tmVwwb$;_2zB|Byy`_iRp}09J&Wd6;lx(lSZiBgJr&m&oK^UwDd%;W-ooZQ zj_2ai=XMM|ZIWV-XiH-#f6PH|^8!6&c(1|^4bV=6z~yceoM7DxNxG#_CdDHvzKQ_@ zbqrx5o)52+@k&v$tnB@iFx=62Qs!1=U2Kc?vkxL`uJ8UxA{6*XDeNCJ&<>wbA=|xH z^5{w_vSwdWiipEA`?)vB{z~F@+WsN3`n$Sabu8pd!&5=zo|L!yHV@Wm8N7H>vcsIs zb=yY+X6*!6P$2VH9?za#z>cmD@sPKHROAFKV|-H!G@1H7j|J zs&xcbjw}|!>)nmh;!n>U@8(rzBfxq;rL z;l({)FDdLi4h>=`RcPS?;|wBSglzy1Y!Z9$$aKolv;|9wSuENp?crt9%9=>;)e_ul zFCWU-7?rd+u-G{cl&Wdp1K#jS4AB*%zH{jL90jb66prcL8x?YTGWp1|;O~hFGqAaM zoL`Fz{A3m7Cn~|jz}V6RacsZBglASq^dp zv2v;Ebz>Dutw&;f!`=&hf!1yOLND-Wb5ij4p0Hllyr&IsK zD7^x56fO0>cwqE;kS6?a>%Eix^GSrzl`cf!7>N5ozWclfv8Vc8pOH^eiZ4e(h$H1m z_EH+AMavOR*#_hvRCHia=?(+dwI&tW(2COi7FnY~XDdt-2CY^?GdZ!%#M-SRs}8Mo zwvv;-zky7(RKJBF08qgIAi;lw;NTz+bvJike|6`80F-wy+jb!NnWhB!CSlbX5H0?I zR=orMr#x{Cp<^zY3t=_4`(VJIm0Pa zq`aSFTZhM-qq5xLJ1R)jY31>LLg6dXGA|Ay!#A!~OylnR5tpNMah>{`F6)-j3`R#p zUYWnp{}^4^8*oMinKHWGTDfzb9tHy^XqFh9$|4uVsrnX+7^yT8W)+h>0L=|O>O&20D%&8o)?d4+P3khwlf*5i( z8@_E@Hbx=Eh%|ySrVwET>xxt!;5!hf*;S{-CpyF)3+GpZSs7_+BQV2;c}H_quf#jg z+Y=r(&7-w0SJjEdHjNW8T2dNdqlFFIsRe>06CK{dm%=HEofi!qS>zX{;C0t>He%{= znPa3eQ9dk9wHR6BR?pC0M&R7El;1iV=4WQ9F8JKk85W&tdhJwyO>w7%6FY5o z0utBeaw3KJTFo0^l#8S}nL6_wXICcS!{ zj%p4$_Vi&nB-i)M=4IQnbOt?iLgS^scW_1YWOQkghM>oxbM`hcB}E71Zal{T_56T3 zOML!I`K+O`ri_jbdHiaAm+(t+gVr{+1=otIasP%JD|)1!bbOv`$Fkn(ngYwUSldKq zJ0eImC$si5OMF}|<&~mjeuG&s?lDu(lJ1UA4p{pv0`S??BwNW7H`;RknT$M7pKW)v zYsAi!NceE~q}6E=iR6j-rs#JHW}V(&nOi0qBU>5|dcIjenoE-{%>dc^@OjYli#EA ZQ(wOQd4jOWx6ldkXAu9VN&X(&e*oK$SIqzb diff --git a/scenarios/ec2_ssrf/assets/ssrf_app/install.sh b/scenarios/ec2_ssrf/assets/ssrf_app/install.sh deleted file mode 100755 index 4e97bd83..00000000 --- a/scenarios/ec2_ssrf/assets/ssrf_app/install.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -echo "[*] Installing Node and NPM" -apt-get update -apt-get -y install nodejs npm -echo "[*] Installing node packages" -npm install http express needle command-line-args -echo -echo "[*] Install complete!" -echo -echo " To start the server:" -echo " sudo nodejs ssrf-demo-app.js" -echo " sudo nodejs ssrf-demo-app.js -p 8080" -echo diff --git a/scenarios/ec2_ssrf/assets/ssrf_app/package.json b/scenarios/ec2_ssrf/assets/ssrf_app/package.json new file mode 100644 index 00000000..f789ef64 --- /dev/null +++ b/scenarios/ec2_ssrf/assets/ssrf_app/package.json @@ -0,0 +1,23 @@ +{ + "name": "ssrf_app", + "version": "1.0.0", + "description": "NodeJS Web App with a SSRF vulnerability", + "main": "app.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/sethsec/Nodejs-SSRF-App.git" + }, + "author": "Seth Art", + "license": "MIT", + "bugs": { + "url": "https://github.com/sethsec/Nodejs-SSRF-App/issues" + }, + "homepage": "https://github.com/sethsec/Nodejs-SSRF-App#readme", + "dependencies": { + "express": "^4.19.2", + "needle": "^3.3.1" + } +} diff --git a/scenarios/ec2_ssrf/assets/ssrf_app/ssrf-demo-app.js b/scenarios/ec2_ssrf/assets/ssrf_app/ssrf-demo-app.js deleted file mode 100644 index 000c8d9f..00000000 --- a/scenarios/ec2_ssrf/assets/ssrf_app/ssrf-demo-app.js +++ /dev/null @@ -1,69 +0,0 @@ -////////////////////////////////////////// -// SSRF Demo App -// Node.js Application Vulnerable to SSRF -// Written by Seth Art -// MIT Licensed -////////////////////////////////////////// - -var http = require('http'); -var needle = require('needle'); -var express = require('express'); -var app = express(); -var commandLineArgs = require('command-line-args'); - -// Currently this app is also vulnerable to reflective XSS as well. Kind of an easter egg :) - -var cli = [ - { name: 'port', alias: 'p', type: Number, defaultOption:80 } -] -var options = commandLineArgs(cli) - -app.get('/', function(request, response){ - var params = request.params; - var url = request.query['url']; - if (request.query['mime'] == 'plain'){ - var mime = 'plain'; - } else { - var mime = 'html'; - }; - - console.log('New request: '+request.url); - - // If the URL is not set, then we will just return the default page. - if (url == undefined) { - response.writeHead(200, {'Content-Type': 'text/'+mime}); - response.write('

Welcome to sethsec\'s SSRF demo.

\n\n'); - response.write('

I am an application. I want to be useful, so give me a URL to requested for you\n



\n\n\n'); - response.end(); - } else { // If the URL is set, then we will try to request it. - needle.get(url, { timeout: 3000 }, function(error, response1) { - // If the request is successful, then we will return the response to the user. - if (!error && response1.statusCode == 200) { - response.writeHead(200, {'Content-Type': 'text/'+mime}); - response.write('

Welcome to sethsec\'s SSRF demo.

\n\n'); - response.write('

I am an application. I want to be useful, so I requested: '+url+' for you\n



\n\n\n'); - console.log(response1.body); - response.write(response1.body); - response.end(); - } else { // If the request is not successful, then we will return an error to the user. - response.writeHead(404, {'Content-Type': 'text/'+mime}); - response.write('

Welcome to sethsec\'s SSRF demo.

\n\n'); - response.write('

I wanted to be useful, but I could not find: '+url+' for you\n



\n\n\n'); - response.end(); - console.log('error') - - } - }); - } -}) -if (options.port) { - var port = options.port -} else { - var port = 80 -} - -app.listen(port); -console.log('\n##################################################') -console.log('#\n# Server listening for connections on port:'+port); -console.log('# Connect to server using the following url: \n# -- http://[server]:'+port+'/?url=[SSRF URL]') -console.log('#\n##################################################') diff --git a/scenarios/ec2_ssrf/terraform/data.tf b/scenarios/ec2_ssrf/terraform/data.tf new file mode 100644 index 00000000..f3a0472d --- /dev/null +++ b/scenarios/ec2_ssrf/terraform/data.tf @@ -0,0 +1,31 @@ +data "aws_ami" "ec2" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } + + filter { + name = "architecture" + values = ["x86_64"] + } +} + +data "archive_file" "lambda_function" { + type = "zip" + source_file = "../assets/lambda.py" + output_path = "../assets/lambda.zip" +} + +data "archive_file" "app" { + type = "zip" + source_dir = "../assets/ssrf_app/" + output_path = "../assets/app.zip" +} diff --git a/scenarios/ec2_ssrf/terraform/data_sources.tf b/scenarios/ec2_ssrf/terraform/data_sources.tf deleted file mode 100644 index cd2b10ab..00000000 --- a/scenarios/ec2_ssrf/terraform/data_sources.tf +++ /dev/null @@ -1,4 +0,0 @@ -#AWS Account Id -data "aws_caller_identity" "aws-account-id" { - -} \ No newline at end of file diff --git a/scenarios/ec2_ssrf/terraform/ec2.tf b/scenarios/ec2_ssrf/terraform/ec2.tf index c347caca..a5ee999b 100644 --- a/scenarios/ec2_ssrf/terraform/ec2.tf +++ b/scenarios/ec2_ssrf/terraform/ec2.tf @@ -1,162 +1,140 @@ -#IAM Role -resource "aws_iam_role" "cg-ec2-role" { - name = "cg-ec2-role-${var.cgid}" - assume_role_policy = <> /etc/crontab - EOF - volume_tags = { - Name = "CloudGoat ${var.cgid} EC2 Instance Root Device" - Stack = "${var.stack-name}" - Scenario = "${var.scenario-name}" - } - tags = { - Name = "cg-ubuntu-ec2-${var.cgid}" - Stack = "${var.stack-name}" - Scenario = "${var.scenario-name}" - } + npm install + sudo node app.js & + echo -e "\n* * * * * root node /home/ubuntu/app/app.js &\n* * * * * root sleep 10; node /home/ubuntu/app/app.js &\n* * * * * root sleep 20; node /home/ubuntu/app/app.js &\n* * * * * root sleep 30; node /home/ubuntu/app/app.js &\n* * * * * root sleep 40; node /home/ubuntu/app/app.js &\n* * * * * root sleep 50; node /home/ubuntu/app/app.js &\n" >> /etc/crontab + EOF + + volume_tags = { + Name = "CloudGoat ${var.cgid} EC2 Instance Root Device" + } + tags = { + Name = "cg-ubuntu-ec2-${var.cgid}" + } } diff --git a/scenarios/ec2_ssrf/terraform/iam.tf b/scenarios/ec2_ssrf/terraform/iam.tf index 4db6e9a2..475b2b13 100644 --- a/scenarios/ec2_ssrf/terraform/iam.tf +++ b/scenarios/ec2_ssrf/terraform/iam.tf @@ -1,108 +1,95 @@ -#IAM Users -resource "aws_iam_user" "cg-solus" { +resource "aws_iam_user" "solus" { name = "solus-${var.cgid}" - tags = { - Name = "cg-solus-${var.cgid}" - Stack = "${var.stack-name}" - Scenario = "${var.scenario-name}" - } } -resource "aws_iam_access_key" "cg-solus" { - user = "${aws_iam_user.cg-solus.name}" +resource "aws_iam_access_key" "solus" { + user = aws_iam_user.solus.name } -resource "aws_iam_user" "cg-wrex" { - name = "wrex-${var.cgid}" - tags = { - Name = "cg-wrex-${var.cgid}" - Stack = "${var.stack-name}" - Scenario = "${var.scenario-name}" - } + +resource "aws_iam_policy" "solus" { + name = "cg-solus-policy-${var.cgid}" + description = "IAM policy for the solus user" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "solus" + Effect = "Allow" + Action = [ + "lambda:Get*", + "lambda:List*" + ] + Resource = "*" + } + ] + }) } -resource "aws_iam_access_key" "cg-wrex" { - user = "${aws_iam_user.cg-wrex.name}" + +resource "aws_iam_user_policy_attachment" "solus" { + user = aws_iam_user.solus.name + policy_arn = aws_iam_policy.solus.arn } -resource "aws_iam_user" "cg-shepard" { - name = "shepard-${var.cgid}" - tags = { - Name = "cg-shepard-${var.cgid}" - Stack = "${var.stack-name}" - Scenario = "${var.scenario-name}" - } + + +resource "aws_iam_user" "wrex" { + name = "wrex-${var.cgid}" } -resource "aws_iam_access_key" "cg-shepard" { - user = "${aws_iam_user.cg-shepard.name}" +resource "aws_iam_access_key" "wrex" { + user = aws_iam_user.wrex.name } -#IAM User Policies -resource "aws_iam_policy" "cg-solus-policy" { - name = "cg-solus-policy-${var.cgid}" - description = "cg-solus-policy-${var.cgid}" - policy = <