PoC || GTFO: #Docker + #Traefik + #Jenkins + #OWASP — BB 0.8
N.B: [This paper is not].append[‘Cloud/InfoSec Tutorial/Workshop’].
It should only be considered as a “Spontaneous Prose” of an ongoing journey, about building a bridge between InfoSec and DevOps industries through #SE, #Cloud, #OpenSource and #BugBounty. After all, it’s only full of open-source ideas shared in CC BY-NC 3.0.
Excuse my French, Kudos for challenging.
TL;DR: RedPill below, BluePill by the end
الواجب على الناظر في كتب العلوم، إذا كان غرضه معرفة الحقائق، أن يجعل نفسه خصماً لكل ما ينظر فيه
أبو علي الحسن بن الحسن بن الهيثم
Oh hai o/
Let’s cut it straight to technical shenanigans.
This PoC is an attempt to implement this wishful aim of InfoSec prevention, as close as legally possible to the Code Factory.
@jeremiahg @Hi_T_ch @ErrataRob @BrendanEich it's waterfall. Waterfall is over.
— Dan Kaminsky (@dakami) March 10, 2016
100% Open Source tech legos. Because the 2 muchData 1 API is not my cup of tea.
It needs to run on cheap servers. Coz I’m a 99% dealing with a lawsuit.
KISS and lazy coding. Don’t judge the dev that I’m not.
Monitoring, Registry management and image scanning are out of scope, because I need to submit this paper tonight.
A vulnerable app,
A kinda State-Diagram, waterfall-proof \o/
user@yogosha:~/code/PoC$ tree
.
├── etc
│ └── traefik.toml <- Load Balancer config
├── Jenkinsfile <- Pipeline as Code
├── LICENSE <- MIT
├── Makefile <- RTFM
├── README.md <- You are reading it
└── src
├── php <- PHP sqli vulnerable
│ ├── debian.png
│ ├── Dockerfile <- Build recipe
│ ├── docker.png
│ ├── index.php <- App Code
│ ├── jenkins.png
│ ├── lamp.jpg
│ ├── owasp.png
│ ├── scaleway.svg
│ ├── star.png
│ └── traefik.png
└── sql
├── production.sql <- Prod Data
└── staging.sql <- Staging Data
4 directories, 17 files
Get yourself comfortable behind your favorite terminal, and install docker.
Start by cloning the repo, make build Dev environment & make it run.
You should get this webapp running and vulnerable to a SQL Injection.
Happy with it? Let’s call our friends, Traefik & Jenkins Pipeline, by running the commands:
docker run -d -p 8666:8080 -p 80:80 -p 443:443
-v /var/run/docker.sock:/var/run/docker.sock
-v traefik:/data -v $PWD/etc/traefik.toml:/traefik.toml
-v $PWD/conf/acme.json:/acme.json traefik
docker run -p 8080 -d -v /data/jenkins/var/jenkins_home:/var/jenkins_home
-v /var/run/docker.sock:/var/run/docker.sock
-v $(which docker):/usr/bin/docker
-v $(which make):/usr/bin/make
--label traefik.backend='jenkins'
--label traefik.port='8080'
--label traefik.protocol='http'
--label traefik.weight='10'
--label traefik.frontend.rule='Host:chocobo.yogosha.com'
--label traefik.frontend.passHostHeader='true'
--label traefik.priority='10'
jenkinsci/docker-workflow-demo
Create a new Pipeline project in Jenkins as following:
Congrats. You’ve run your first DevOpsSec process \o/
No tricks. Only code. One file that mimics the initial State Diagram.
node{
def appname = "redpill"
stage 'Checkout'
git url: 'https://www.github.com/h-a-t/RedPill'
stage 'Pull img'
sh 'make pull'
stage 'Test build'
sh 'make build'
stage('SonarQube analysis') {
// requires SonarQube Scanner 2.8+
def scannerHome = tool 'SonarQubeScanner';
sh "ls -la"
withSonarQubeEnv('SonarQube') {
sh "${scannerHome}/bin/sonar-scanner -D sonar.projectKey=${appname} -D sonar.sources=src/php/"
}
}
stage 'Building env'
def state = "staging"
// Puts app stuff
def busy = docker.image('busybox');
busy.inside("-v ${appname}-app:/data"){
sh "cp -r ./src/php/* /data/"
sh "chown -R www-data:www-data /data"
}
// Puts db stuff
busy.inside("-v ${appname}-${state}-db:/data"){
sh "rm -rf /data/*"
sh "cp -r ./src/sql/staging.sql /data/"
}
// Prepares containers
def php = docker.build("${appname}-app", './src/php/')
def zap = docker.image('owasp/zap2docker-weekly')
def db = docker.image('mysql/mysql-server:5.6')
// Defines staging param
def db_param = "-e 'MYSQL_RANDOM_ROOT_PASSWORD=yes' -e 'MYSQL_USER=user' -e 'MYSQL_PASSWORD=password' -e 'MYSQL_DATABASE=sqli' --label 'traefik.enable=false'"
def app_param = "--label traefik.backend='app-${state}' --label traefik.port='80' --label traefik.protocol='http' --label traefik.weight='10' --label traefik.frontend.rule='Host:${state}.chocobo.yogosha.com' --label traefik.frontend.passHostHeader='true' --label traefik.priority='10' -e BUILD_STAGE=${state}"
// Starts Staging instances
def staging_db = db.run("${db_param} -v ${appname}-${state}-db:/docker-entrypoint-initdb.d/")
def staging_app = php.run ("-P ${app_param} -v ${appname}-app:/var/www/html --link ${staging_db.id}:db")
// Runs quick security check
stage 'Test with OWASP ZapProxy'
zap.inside("--link ${staging_app.id}:app -v ${appname}-zap:/zap/wrk") {
println('Waiting for server to be ready')
sh "until \$(curl --output /dev/null --silent --head --fail http://app/index.php); do printf '.'; sleep 5; done"
println('It Works!')
sh "ls -la"
// Active scan, a bit more permissive and customizable
sh "zap-cli quick-scan --self-contained --start-options '-config api.disablekey=true' http://app/index.php"
// Passive scan, build will fail if any WARN is returned -> #blame.
// sh "zap-baseline.py -t http://app/index.php -r report.html"
}
// "Oh hai o/ Can I haz a quality check?"
stage 'QA'
input "Is https://${state}.chocobo.yogosha.com going according to plan?"
staging_app.stop()
// "N33d Help for securitay?"
stage 'Bug Bounty'
// Defines bb param
state = "bb"
app_param = "--label traefik.backend='app-${state}' --label traefik.port='80' --label traefik.protocol='http' --label traefik.weight='10' --label traefik.frontend.rule='Host:${state}.chocobo.yogosha.com' --label traefik.frontend.passHostHeader='true' --label traefik.priority='10' -e BUILD_STAGE=${state}"
def bb_app = php.run ("-P ${app_param} -v ${appname}-app:/var/www/html --link ${staging_db.id}:db")
stage 'Production'
// Loading a snapshot of Production data
state = "prod"
busy.inside("-v ${appname}-${state}-db:/data"){
sh "rm -rf /data/*"
sh "cp -r ./src/sql/production.sql /data/"
}
// Push to the interwebz!
app_param = "--label traefik.backend='app-${state}' --label traefik.port='80' --label traefik.protocol='http' --label traefik.weight='10' --label traefik.frontend.rule='Host:${state}.chocobo.yogosha.com' --label traefik.frontend.passHostHeader='true' --label traefik.priority='10' -e BUILD_STAGE=${state}"
db_param = "-e MYSQL_RANDOM_ROOT_PASSWORD=yes -e MYSQL_USER=user -e MYSQL_PASSWORD=p4s5w0rd -e 'MYSQL_DATABASE=sqli' --label traefik.enable=false"
def prod_db = db.run("${db_param} -v ${appname}-${state}-db:/docker-entrypoint-initdb.d/")
def prod_app = php.run ("-d -P ${app_param} -v ${appname}-app:/var/www/html --link ${prod_db.id}:db")
}
In a nutshell, it’s only doing what you just did, runs a security test using ZAP on it, then push it to registry.
To sum up:
This is your last chance. After this, there is no turning back. You take the blue pill — the story ends, you wake up in your bed and believe whatever you want to believe. You take the red pill — you stay in Wonderland, and I show you how deep the rabbit hole goes. Remember: all I’m offering is the truth. Nothing more.