├── .circleci ├── config.yml └── config.yml.complete ├── .github └── workflows │ └── pixi-crs-ci.yml ├── LICENSE ├── README.md ├── RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf ├── SECURITY.md ├── azure-pipelines.yml ├── buildspec.yml ├── cloudbuild.yaml ├── compose-aws.env ├── compose-gcp.env ├── compose-local.env ├── docker-compose-without-crs.yaml ├── docker-compose.yaml ├── img ├── AWS │ ├── AWS_CodeBuild_Logs.png │ ├── AWS_Pipeline_Output.png │ ├── AWS_PixiCRS_BuildProject.png │ ├── AWS_Testcafe_Output.png │ └── AWS_Title_Image.png ├── AzureDevOps │ ├── Azure_Pipeline_Output.png │ ├── Azure_Testcafe_Output.png │ ├── Azure_Title_Image.png │ ├── azure-1-new-project.png │ ├── azure-2-create-pipeline.png │ ├── azure-3-github.png │ ├── azure-4-select-repo.png │ └── azure-5-review-pipeline.png ├── CircleCI │ └── CircleCI_Pipeline_Output.png ├── GCP │ ├── GCP_Testcafe_Output.png │ └── GCP_Title_Image.png └── GitHub_Actions │ ├── GHA_Pipeline_Output.png │ └── GHA_Testcafe_Output.png ├── testcafe ├── tests_container_ip │ ├── test.js │ ├── testcrs.js │ └── testwaf.js ├── tests_container_ip2 │ ├── mongodb.js │ ├── mongodbcrs.js │ ├── test.js │ ├── test_local.js │ ├── testcrs-match.js │ ├── testcrs.js │ └── testwaf.js └── tests_localhost │ ├── test.js │ ├── testcrs.js │ └── testwaf.js └── zap ├── zap-api.conf └── zap-baseline.conf /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Getting started with CircleCI 2 | # https://circleci.com/docs/2.0/first-steps/ 3 | # 4 | # .circleci/config.yml 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | - image: circleci/node:stretch 10 | 11 | steps: 12 | - run: 13 | name: Install dependencies 14 | command: | 15 | sudo apt-get update && sudo apt-get install -y curl 16 | mkdir -p /tmp/test-results/ 17 | 18 | - run: 19 | name: Install Docker Compose 20 | command: | 21 | curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` > ~/docker-compose 22 | chmod +x ~/docker-compose 23 | sudo mv ~/docker-compose /usr/local/bin/docker-compose 24 | 25 | - setup_remote_docker 26 | 27 | - checkout 28 | 29 | - run: 30 | name: Start Application Pixi with docker-compose up 31 | # http://172.17.0.1:8000 32 | command: | 33 | set -x 34 | docker-compose version 35 | docker-compose -f docker-compose-without-crs.yaml up -d 36 | 37 | # OWASP ModSecurity Core Rule Set Container (Apache Reverse Proxy) 38 | # owasp/modsecurity-crs 39 | # Environment variables: 40 | # PARANOIA: paranoia_level 41 | # ANOMALYIN: inbound_anomaly_score_threshold 42 | # ANOMALYOUT: outbound_anomaly_score_threshold 43 | # ALLOWED_METHODS: A string indicating the allowed_methods (Default: GET HEAD POST OPTIONS) 44 | # See https://coreruleset.org/ 45 | # 46 | # PROXYLOCATION: application backend 47 | - run: 48 | name: Start OWASP ModSecurity CRS Container in front Pixi 49 | # http://172.17.0.2:80 50 | # we set inbound and outbound anomaly score to 1, no tolerance 51 | # We have to expose the port here, port mapping is not supported in CircleCI 52 | command: | 53 | docker pull owasp/modsecurity-crs:v3.3.0-apache && \ 54 | docker run -dt --name crs -e PARANOIA=2 \ 55 | -e ALLOWED_METHODS='GET HEAD POST OPTIONS PUT' -e \ 56 | ANOMALY_INBOUND=1 -e ANOMALY_OUTBOUND=1 -e BACKEND=http://172.17.0.1:8000 \ 57 | -e ERRORLOG=/var/log/apache2/error.log \ 58 | --expose 80 owasp/modsecurity-crs:v3.3.0-apache 59 | 60 | # ModSecurity Tuning:\ 61 | # See https://www.netnea.com/cms/apache-tutorial-8_handling-false-positives-modsecurity-core-rule-set/ 62 | # We use rule exclusion example files: 63 | # REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf OR 64 | # RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf 65 | # We use cp command here because volumes are not supported in CircleCI 66 | - run: 67 | name: ModSecurity Tuning - Load rule exclusions 68 | command: | 69 | docker cp RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf crs:/etc/modsecurity.d/owasp-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf; 70 | docker exec crs /usr/local/apache2/bin/apachectl -f /usr/local/apache2/conf/httpd.conf -k graceful 71 | 72 | # Debugging possibilities: 73 | # - run: 74 | # name: curl test 75 | # command: | 76 | # ( 77 | # docker pull hiromasaono/curl && \ 78 | # docker run -dt --name curl hiromasaono/curl && \ 79 | # docker exec -ti curl curl http://172.17.0.1:8000/register 80 | # docker exec -ti curl curl http://172.17.0.2:8001/register 81 | # docker exec -ti apachecrs cat /var/log/apache2/error.log 82 | # docker exec -ti apachecrs curl 172.17.0.1:8000 83 | # ) 84 | # 85 | - run: 86 | name: Application Tests with Testcafe 87 | command: | 88 | # https://circleci.com/docs/2.0/building-docker-images/#mounting-folders 89 | # creating dummy container which will hold a volume with config 90 | docker create -v /tests --name configs alpine:latest /bin/true 91 | # copying config file into this volume 92 | docker cp /home/circleci/project/testcafe/tests_container_ip2/test.js configs:/tests 93 | #docker cp /home/circleci/project/testcafe/tests/mongodb.js configs:/tests 94 | # starting application container using this volume 95 | docker pull testcafe/testcafe 96 | # skip-js-errors because of: Uncaught Error: Bootstrap tooltips require Tether 97 | docker run --volumes-from configs:rw --name testcafe -it testcafe/testcafe --skip-js-errors 'chromium:headless --no-sandbox' /tests/test.js #/tests/mongodb.js 98 | # docker cp testcafe:/tmp/res.xml /tmp/test-results/ 99 | # docker run --volumes-from configs:rw --name testcafe -it testcafe/testcafe --reporter xunit:/tmp/res.xml --skip-js-errors 'chromium:headless --no-sandbox' /tests/test.js /tests/mongodb.js 100 | 101 | - run: 102 | name: Application Tests with CRS with Testcafe 103 | command: | 104 | docker cp /home/circleci/project/testcafe/tests_container_ip2/testcrs.js configs:/tests 105 | # Intentionally trigger an error with a Testcafe Test that calls $select in ARGS_GET: 106 | # docker cp /home/circleci/project/testcafe/tests/testcrs-match.js configs:/tests 107 | # Some evil Mongo DB statements: 108 | # docker cp /home/circleci/project/testcafe/tests/mongodbcrs.js configs:/tests 109 | # skip-js-errors because of: Uncaught Error: Bootstrap tooltips require Tether 110 | docker run --volumes-from configs:rw --name testcafecrs -it testcafe/testcafe --skip-js-errors 'chromium:headless --no-sandbox' /tests/testcrs.js #/tests/testcrs-match.js /tests/mongodbcrs.js 111 | # docker cp testcafe:/tmp/res.xml /tmp/test-results/ 112 | # docker run -v /home/circleci/project/testcafe/tests:/tests -it testcafe/testcafe chrome /tests/testcrs.js --skip-js-errors --reporter xunit:/tmp/test-results/res.xml 113 | 114 | - run: 115 | name: WAF Tests with malicous request to test WAF itself 116 | command: | 117 | docker cp /home/circleci/project/testcafe/tests_container_ip2/testwaf.js configs:/tests 118 | # skip-js-errors because of: Uncaught Error: Bootstrap tooltips require Tether 119 | docker run --volumes-from configs:rw --name testcafewaf -it testcafe/testcafe --skip-js-errors 'chromium:headless --no-sandbox' /tests/testwaf.js 120 | 121 | - run: 122 | # Fail if ModSecurity log is not empty 123 | # If not empty -> Repair your application OR 124 | # -> ModSecurity Tuning: 125 | # See https://www.netnea.com/cms/apache-tutorial-8_handling-false-positives-modsecurity-core-rule-set/ OR 126 | # -> GitHub issue: https://github.com/SpiderLabs/owasp-modsecurity-crs 127 | name: Show ModSecurity logs of Testcafe Tests 128 | command: | 129 | docker exec crs cat /var/log/apache2/error.log \ 130 | | grep ModSecurity | grep -vi "My evil WAF Test" | grep -v " Score: 30" | grep msg && exit 1 || exit 0 131 | 132 | 133 | # we always want to see this step: 134 | when: always 135 | 136 | - run: 137 | # Fail if ModSecurity log does not contain WAF Test String "My evil WAF Test" 138 | # '' 139 | # If empty -> WAF Test String 140 | # did not trigger a CRS rule. 141 | # That means CRS is not working properly or test was aborted. 142 | name: Search for WAF Test String "My evil WAF Test" in ModSecurity logs 143 | command: | 144 | docker exec crs cat /var/log/apache2/error.log 145 | 146 | # we always want to see this step: 147 | when: always 148 | 149 | - store_test_results: 150 | path: /tmp/test-results 151 | -------------------------------------------------------------------------------- /.circleci/config.yml.complete: -------------------------------------------------------------------------------- 1 | # Getting started with CircleCI 2 | # https://circleci.com/docs/2.0/first-steps/ 3 | # 4 | # .circleci/config.yml 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | - image: circleci/node:9.5.0-stretch 10 | 11 | steps: 12 | - run: 13 | name: Install dependencies 14 | command: | 15 | sudo apt-get update && sudo apt-get install -y curl 16 | mkdir -p /tmp/test-results/ 17 | 18 | - run: 19 | name: Install Docker Compose 20 | command: | 21 | curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` > ~/docker-compose 22 | chmod +x ~/docker-compose 23 | sudo mv ~/docker-compose /usr/local/bin/docker-compose 24 | 25 | - setup_remote_docker 26 | 27 | - checkout 28 | 29 | - run: 30 | name: Start App Container 31 | #http://172.17.0.1:8000 32 | command: | 33 | set -x 34 | docker-compose version 35 | docker-compose up -d 36 | 37 | # OWASP ModSecurity Core Rule Set Container (Apache Reverse Proxy) 38 | # franbuehler/modsecurity-crs-rp 39 | # Environment variables: 40 | # PARANOIA: paranoia_level 41 | # ANOMALYIN: inbound_anomaly_score_threshold 42 | # ANOMALYOUT: outbound_anomaly_score_threshold 43 | # See https://coreruleset.org/ 44 | # 45 | # BACKEND: application backend 46 | # PORT: listening port of apache, this port must be exposed: --expose 47 | - run: 48 | name: Start OWASP ModSecurity CRS Container in front of application for application tests 49 | #http://172.17.0.2:8001 50 | #we set inbound and outbound anomaly score to 1, no tolerance 51 | command: | 52 | docker login -u ${DOCKER_USER} -p ${DOCKER_PASS}; 53 | docker pull franbuehler/modsecurity-crs-rp && \ 54 | docker run -dt --name apachecrstc -e PARANOIA=2 -e \ 55 | ANOMALYIN=1 -e ANOMALYOUT=1 -e BACKEND=http://172.17.0.1:8000 \ 56 | -e PORT=8001 --expose 8001 franbuehler/modsecurity-crs-rp 57 | 58 | #docker run -dt --name apachecrs franbuehler/modsecurity-crs-rp 59 | 60 | # - run: 61 | # name: Start OWASP ModSecurity CRS Container in front of application for mean application tests 62 | # #http://172.17.0.3:8002 63 | # #we set inbound and outbound anomaly score to 1, no tolerance 64 | # command: | 65 | # docker login -u ${DOCKER_USER} -p ${DOCKER_PASS}; 66 | # docker pull franbuehler/modsecurity-crs-rp && \ 67 | # docker run -dt --name apachecrsmeantc -e PARANOIA=2 -e \ 68 | # ANOMALYIN=100 -e ANOMALYOUT=100 -e BACKEND=http://172.17.0.1:8000 \ 69 | # -e PORT=8002 --expose 8002 franbuehler/modsecurity-crs-rp 70 | 71 | # - run: 72 | # name: Start OWASP ModSecurity CRS Container in front of API for ZAP Scan 73 | # #http://172.17.0.4:8003 74 | # command: | 75 | # docker run -dt --name apachecrsapi -e PARANOIA=1 -e \ 76 | # ANOMALYIN=1 -e ANOMALYOUT=1 -e BACKEND=http://172.17.0.1:8090 \ 77 | # -e PORT=8003 --expose 8003 franbuehler/modsecurity-crs-rp 78 | 79 | # - run: 80 | # name: Start OWASP ModSecurity CRS Container in front of application for ZAP Active Scan 81 | # #http://172.17.0.4:8003 82 | # #we set inbound and outbound anomaly score to 100, we want to see all errors 83 | # command: | 84 | # docker run -dt --name apachecrsactive -e PARANOIA=4 -e \ 85 | # ANOMALYIN=1 -e ANOMALYOUT=1 -e BACKEND=http://172.17.0.1:8000 \ 86 | # -e PORT=8003 --expose 8003 franbuehler/modsecurity-crs-rp 87 | 88 | # ModSecurity Tuning: 89 | # See https://www.netnea.com/cms/apache-tutorial-8_handling-false-positives-modsecurity-core-rule-set/ 90 | # We use rule exclusion example files: 91 | # REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf OR 92 | # RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf 93 | - run: 94 | name: ModSecurity Tuning - Load rule exclusions 95 | command: | 96 | # rule 920350 (msg: Host header is a numeric IP address) triggers, 97 | # because we use IP addresses instead of hostnames. 98 | # This rule must not be excluded in production! 99 | printf "\nSecRuleRemoveById 920350\n" > tmp.conf 100 | # CRS container for application tests: 101 | docker cp tmp.conf apachecrstc:/etc/httpd/modsecurity.d/owasp-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf; 102 | docker exec apachecrstc /usr/sbin/httpd -k graceful 103 | # CRS container for application tests: 104 | # docker cp tmp.conf apachecrsmeantc:/etc/httpd/modsecurity.d/owasp-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf; 105 | # docker exec apachecrsmeantc /usr/sbin/httpd -k graceful 106 | # CRS container for ZAP API Scan: 107 | # docker cp tmp.conf apachecrsapi:/etc/httpd/modsecurity.d/owasp-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf; 108 | # docker exec apachecrsapi /usr/sbin/httpd -k graceful 109 | # CRS container for ZAP Scan: 110 | # docker cp tmp.conf apachecrsactive:/etc/httpd/modsecurity.d/owasp-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf; 111 | # docker exec apachecrsactive /usr/sbin/httpd -k graceful 112 | # 113 | # Examples: 114 | # printf "\nSecRuleUpdateTargetById 942420 \"!REQUEST_COOKIES:session\"\n" > tmp.conf 115 | # printf "\nSecRuleUpdateTargetById 942440 \"!REQUEST_COOKIES:session\"\n" > tmp.conf 116 | # 117 | # - run: 118 | # name: ZAP Baseline Scan of application (without CRS) 119 | # command: | 120 | # ( 121 | # docker pull owasp/zap2docker-weekly && \ 122 | # docker run -t owasp/zap2docker-weekly zap-baseline.py \ 123 | # -t http://172.17.0.1:8000/ -l WARN || \ 124 | # if [ $? -ne 1 ]; then exit 0; else exit 1; fi; 125 | # ) 126 | 127 | # # https://hub.docker.com/r/ictu/zap2docker-weekly/ 128 | # # This Active Scan has to be developed: a custom config_file and context file 129 | # # This Active Scan takes about 3,5 minutes to run 130 | # - run: 131 | # name: ZAP Active Scan of application (without CRS) 132 | # command: | 133 | # ( 134 | # docker pull ictu/zap2docker-weekly && \ 135 | # docker run -t ictu/zap2docker-weekly zap-baseline-custom.py \ 136 | # -t http://172.17.0.1:8000/ -l WARN -m 5 \ 137 | # --active_scan \ 138 | # --auth_loginurl http://172.17.0.1:8000/login \ 139 | # --auth_usernamefield user --auth_passwordfield pass \ 140 | # --auth_auto --auth_username pixiadmin --auth_password adminpixi || \ 141 | # # we know this is an insecure application: 142 | # exit 0 143 | # #if [ $? -ne 1 ]; then exit 0; else exit 1; fi; 144 | # ) 145 | # 146 | # # https://hub.docker.com/r/ictu/zap2docker-weekly/ 147 | # # This Active Scan has to be developed: a custom config_file and context file 148 | # # This Active Scan takes about 3,5 minutes to run 149 | # - run: 150 | # name: ZAP active scan of application with CRS 151 | # # Only fail on error code 1, which indicates at least one FAIL was found. 152 | # # error codes 2 & 3 indicate WARN or other, and should not break the run 153 | # # -u https://raw.githubusercontent.com/${DOCKER_USER}/${CIRCLE_PROJECT_REPONAME}/master/zap-baseline.conf \ 154 | # command: | 155 | # ( 156 | # docker pull ictu/zap2docker-weekly && \ 157 | # docker run -t ictu/zap2docker-weekly zap-baseline-custom.py \ 158 | # -t http://172.17.0.4:8003/ -l WARN -m 5 \ 159 | # --active_scan \ 160 | # --auth_loginurl http://172.17.0.4:8003/login \ 161 | # --auth_usernamefield user --auth_passwordfield pass \ 162 | # --auth_auto --auth_username pixiadmin --auth_password adminpixi || \ 163 | # # we know this is an insecure application: 164 | # exit 0 165 | # ) 166 | 167 | # Debugging possibilities: 168 | # - run: 169 | # name: curl test 170 | # command: | 171 | # ( 172 | # docker pull hiromasaono/curl && \ 173 | # docker run -dt --name curl hiromasaono/curl && \ 174 | # docker exec -ti curl curl http://172.17.0.1:8000/register 175 | # docker exec -ti curl curl http://172.17.0.2:8001/register 176 | # docker exec -ti apachecrs cat /etc/httpd/logs/error.log 177 | # docker exec -ti apachecrs curl 172.17.0.1:8000 178 | # ) 179 | # 180 | # A zap-api.conf could be developed 181 | # - run: 182 | # name: ZAP API scan of application (without CRS) 183 | # command: | 184 | # ( 185 | # docker pull owasp/zap2docker-weekly && \ 186 | # docker run -t owasp/zap2docker-weekly zap-api-scan.py \ 187 | # -u https://raw.githubusercontent.com/franbuehler/pixi-crs/master/zap-api.conf?token=AQ6RVdBQnWmIHjOULcbaM-1untbAtqkkks5aoZmqwA \ 188 | # -f openapi -l WARN \ 189 | # -t http://172.17.0.1:8090/swagger.json \ 190 | # -z "config formhandler.fields.field\(0\).fieldId=user" \ 191 | # -z "config formhandler.fields.field\(0\).value=test@pixi.owasp" \ 192 | # -z "config formhandler.fields.field\(0\).enabled=true" \ 193 | # -z "config formhandler.fields.field\(1\).fieldId=pass" \ 194 | # -z "config formhandler.fields.field\(1\).value=testpass" \ 195 | # -z "config formhandler.fields.field\(1\).enabled=true" || \ 196 | # # we know this is an insecure application: 197 | # exit 0 198 | # ) 199 | # 200 | # A zap-api.conf could be developed 201 | # - run: 202 | # name: ZAP API scan of application with CRS 203 | # command: | 204 | # ( 205 | # docker pull owasp/zap2docker-weekly && \ 206 | # docker run -t owasp/zap2docker-weekly zap-api-scan.py \ 207 | # -u https://raw.githubusercontent.com/franbuehler/pixi-crs/master/zap-api.conf?token=AQ6RVdBQnWmIHjOULcbaM-1untbAtqkkks5aoZmqwA \ 208 | # -f openapi -l WARN \ 209 | # -t http://172.17.0.4:8003/swagger.json \ 210 | # -z "config formhandler.fields.field\(0\).fieldId=user" \ 211 | # -z "config formhandler.fields.field\(0\).value=test@pixi.owasp" \ 212 | # -z "config formhandler.fields.field\(0\).enabled=true" \ 213 | # -z "config formhandler.fields.field\(1\).fieldId=pass" \ 214 | # -z "config formhandler.fields.field\(1\).value=testpass" \ 215 | # -z "config formhandler.fields.field\(1\).enabled=true" || \ 216 | # if [ $? -ne 1 ]; then exit 0; else exit 1; fi; 217 | # ) 218 | 219 | - run: 220 | name: Application Tests with Testcafe 221 | command: | 222 | # https://circleci.com/docs/2.0/building-docker-images/#mounting-folders 223 | # creating dummy container which will hold a volume with config 224 | docker create -v /tests --name configs alpine:latest /bin/true 225 | # copying config file into this volume 226 | docker cp /home/circleci/project/testcafe/tests/test.js configs:/tests 227 | #docker cp /home/circleci/project/testcafe/tests/mongodb.js configs:/tests 228 | # starting application container using this volume 229 | docker pull testcafe/testcafe 230 | # skip-js-errors because of: Uncaught Error: Bootstrap tooltips require Tether 231 | docker run --volumes-from configs:rw --name testcafe -it testcafe/testcafe --skip-js-errors 'chromium:headless --no-sandbox' /tests/test.js #/tests/mongodb.js 232 | # docker cp testcafe:/tmp/res.xml /tmp/test-results/ 233 | # docker run --volumes-from configs:rw --name testcafe -it testcafe/testcafe --reporter xunit:/tmp/res.xml --skip-js-errors 'chromium:headless --no-sandbox' /tests/test.js /tests/mongodb.js 234 | 235 | - run: 236 | name: Application Tests with CRS with Testcafe 237 | command: | 238 | docker cp /home/circleci/project/testcafe/tests/testcrs.js configs:/tests 239 | #docker cp /home/circleci/project/testcafe/tests/mongodbcrs.js configs:/tests 240 | # skip-js-errors because of: Uncaught Error: Bootstrap tooltips require Tether 241 | docker run --volumes-from configs:rw --name testcafecrs -it testcafe/testcafe --skip-js-errors 'chromium:headless --no-sandbox' /tests/testcrs.js #/tests/mongodbcrs.js 242 | # docker cp testcafe:/tmp/res.xml /tmp/test-results/ 243 | # docker run -v /home/circleci/project/testcafe/tests:/tests -it testcafe/testcafe chrome /tests/testcrs.js --skip-js-errors --reporter xunit:/tmp/test-results/res.xml 244 | 245 | - run: 246 | # Fail if ModSecurity log is not empty 247 | name: Show ModSecurity logs of Testcafe Tests 248 | command: | 249 | docker exec apachecrstc cat /etc/httpd/logs/error.log 250 | #| grep ModSecurity | grep msg | tee /tmp/test-results/ModSecurity-Testcafe.log || \ 251 | # if [ $? -eq 1 ]; then echo "No ModSecurity rule matched, that is ok!"; exit 0; fi; 252 | # If not empty -> Repair your application OR 253 | # -> ModSecurity Tuning: 254 | # See https://www.netnea.com/cms/apache-tutorial-8_handling-false-positives-modsecurity-core-rule-set/ OR 255 | # -> GitHub issue: https://github.com/SpiderLabs/owasp-modsecurity-crs 256 | # we always want to see this step: 257 | when: always 258 | 259 | # - run: 260 | # name: Show ModSecurity logs of mean Testcafe Tests 261 | # command: | 262 | # docker exec apachecrsmeantc cat /etc/httpd/logs/error.log 263 | # #| grep ModSecurity | grep msg | tee /tmp/test-results/ModSecurity-Testcafe.log || \ 264 | # # if [ $? -eq 1 ]; then echo "No ModSecurity rule matched, that is ok!"; exit 0; fi; 265 | # # we always want to see this step: 266 | # when: always 267 | 268 | # - run: 269 | # name: Show ModSecurity logs of ZAP API Scan 270 | # command: | 271 | # docker exec apachecrsapi cat /etc/httpd/logs/error.log | grep -o -E " (at|against) .*\[file.*\[id \"[0-9]+.*\[msg \"[^\"]+" | tr -d \" | sed -e "s/ at the end of input at/ at/" -e "s/ required. /. /" -e "s/\[rev .*\[msg/[msg/" -e "s/\. / /" -e "s/(Total .*/(Total ...) .../" | tr -d \] | cut -d\ -f3,9,11- | sed -e "s/^\([^ ]*\) \([^ ]*\)/\2 \1/" | awk "{ printf \"%+6s %-35s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n\", \$1, \$2, \$3, \$4, \$5, \$6, \$7, \$8, \$9, \$10, \$11, \$12, \$13, \$14, \$15, \$16, \$17, \$18, \$19, \$20 }" | sed -e "s/\ *$//" | sort | uniq -c | sort -n | tee /tmp/test-results/ModSecurity-ZAP-api-scan.log || \ 272 | # if [ $? -eq 1 ]; then echo "Empty or not existing ModSecurity log from ZAP API scan"; exit 1; else exit 0; fi; 273 | # # we always want to see this step: 274 | # when: always 275 | 276 | # - run: 277 | # name: Show ModSecurity logs of ZAP Active Scan 278 | # command: | 279 | # docker exec apachecrsactive cat /etc/httpd/logs/error.log | grep -o -E " (at|against) .*\[file.*\[id \"[0-9]+.*\[msg \"[^\"]+" | tr -d \" | sed -e "s/ at the end of input at/ at/" -e "s/ required. /. /" -e "s/\[rev .*\[msg/[msg/" -e "s/\. / /" -e "s/(Total .*/(Total ...) .../" | tr -d \] | cut -d\ -f3,9,11- | sed -e "s/^\([^ ]*\) \([^ ]*\)/\2 \1/" | awk "{ printf \"%+6s %-35s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n\", \$1, \$2, \$3, \$4, \$5, \$6, \$7, \$8, \$9, \$10, \$11, \$12, \$13, \$14, \$15, \$16, \$17, \$18, \$19, \$20 }" | sed -e "s/\ *$//" | sort | uniq -c | sort -n | tee /tmp/test-results/ModSecurity-ZAP-active-scan.log || \ 280 | # if [ $? -eq 1 ]; then echo "Empty or not existing ModSecurity log from ZAP Active scan"; exit 1; else exit 0; fi; 281 | # # we always want to see this step: 282 | # when: always 283 | 284 | - store_test_results: 285 | path: /tmp/test-results 286 | -------------------------------------------------------------------------------- /.github/workflows/pixi-crs-ci.yml: -------------------------------------------------------------------------------- 1 | name: Pixi-CRS CI Pipeline 2 | 3 | on: [push, pull_request] 4 | # Trigger the workflow on push, 5 | # but only for the master branch 6 | 7 | jobs: 8 | build: 9 | name: Start Pixi and the CRS 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@master 14 | 15 | # - name: Debugging 16 | # run: pwd 17 | # - name: Debugging 18 | # run: ls 19 | 20 | - name: Starting Pixi and CRS with docker-compose up 21 | run: docker-compose -f docker-compose.yaml --env-file compose-gcp.env up -d 22 | 23 | # Application Tests with Testcafe 24 | # skip-js-errors because of: Uncaught Error: Bootstrap tooltips require Tether 25 | - name: Run Testcafe Tests Pixi without and with CRS 26 | run: docker run --volume /home/runner/work/pixi-crs/pixi-crs/testcafe/tests_container_ip:/tests --rm testcafe/testcafe --skip-js-errors 'chromium:headless --no-sandbox' 27 | 28 | # Show Full error.log 29 | - name: Show ModSecurity logs 30 | run: docker exec crs cat /var/log/apache2/error.log 31 | 32 | # ModSecurity Log Analysis: 33 | # Fail if ModSecurity log is not empty 34 | # Show ModSecurity logs of Testcafe Tests 35 | # If not empty -> Repair your application OR 36 | # -> ModSecurity Tuning: 37 | # See https://www.netnea.com/cms/apache-tutorial-8_handling-false-positives-modsecurity-core-rule-set/ OR 38 | # -> GitHub issue: https://github.com/SpiderLabs/owasp-modsecurity-crs 39 | - name: Fail if ModSecurity logs are not empty 40 | run: if docker exec crs cat /var/log/apache2/error.log | grep ModSecurity | grep -vi MyEvilWAFTest | grep -v 949110 | grep -v 980130 | grep msg; then echo "False Positive Found! Aborting!" && exit 1 ; else echo "ModSecurity Logs empty. This is good!"; fi 41 | 42 | # Fail if ModSecurity log does not contain WAF Test String "MyEvilWAFTest" 43 | # That means CRS is not working properly or test was aborted. 44 | - name: Fail if WAF Test String is missing in ModSecurity logs 45 | run: if docker exec crs cat /var/log/apache2/error.log | grep ModSecurity | grep MyEvilWAFTest; then echo "WAF Test String Found. This is good!"; else echo "WAF Test String not Found! Aborting!" && exit 1; fi 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DevSlop pixi-crs - Integration of OWASP ModSecurity CRS into a CI Pipeline 2 | 3 | This repository is one of DevSlop's modules as described in [devslop.github.io](https://devslop.github.io). 4 | 5 | This repository integrates the WAF ModSecurity with the OWASP ModSecurity Core Rule Set (CRS) and its testing into different CI Pipelines. 6 | Currently the following Pipelines are implemented: 7 | 8 | * CircleCI 9 | * AWS 10 | * Google Cloud Provider 11 | * Azure Pipeline 12 | * GitHub Actions 13 | 14 | The CI Pipelines test (with TestCafe) DevSlop's vulnerable web application Pixi without and with the CRS. 15 | 16 | By adding and testing the WAF in the Continuous Integration (CI) pipeline, we provide the application developer early feedback. The application developers get feedback about how their application will react when behind a WAF. We assure that Pixi’s legitimate traffic is not blocked by the WAF, and that illegitimate traffic is. 17 | 18 | ## Building Blocks of the pixi-crs Pipelines and how they are implemented 19 | 20 | | | CircleCI | AWS | GCP | Azure | GitHub Actions | 21 | | ------------- | -------------------- | --------------------- | ------------- | ------------------------ | --------------------- | 22 | | Code File | .circleci/config.yml | buildspec.yml | cloudbuild.yaml | azure-pipelines.yml | .github/workflows/pixi-crs-ci.yml | 23 | | Start Pixi | docker-compose up | docker-compose up | docker-compose up | docker-compose up | docker-compose up | 24 | | Start CRS | docker run | same docker-compose | same docker-compose | same docker-compose | same docker-compose | 25 | | ModSec Tuning | docker cp | Volume docker-compose | Volume docker-compose | Volume docker-compose | Volume docker-compose | 26 | | Start Testcafe| Testcafe Docker | npm install testcafe | Testcafe Docker | Testcafe Docker | Testcafe Docker | 27 | | Log Analysis | docker exec cat logfile | docker exec cat logfile | docker exec cat logfile | docker exec cat logfile | docker exec cat logfile| 28 | 29 | ## Local Startup of Pixi and CRS 30 | 31 | If you want to start Pixi and the CRS locally you can run: 32 | 33 | `docker-compose --env-file compose-local.env up -d` 34 | 35 | Then you can reach Pixi directly: http://localhost:8000/. 36 | And the WAF with Pixi as the backend: http://localhost:8080/. 37 | 38 | ## Further Reading 39 | 40 | ### Description of the CI Pipeline 41 | * [Pixi-CRS goes to the Cloud: 6 part blog posts series](https://dev.to/franbuehler/series/5552) 42 | * [DevSlop Blog Post on dev.to describing the CircleCI pixi-crs Pipeline](https://dev.to/devslop/devslop-s-pixi-crs-pipeline-4bie) 43 | * [DevSlop Blog Post on dev.to describing how the CRS protects Pixi](https://dev.to/devslop/how-the-owasp-modsecurity-core-rule-set-protects-the-vulnerable-web-application-pixi-by-owasp-devslop-n4d) 44 | 45 | ### Blog Post about Pixi's vulnerabilities and the CRS 46 | * [CRS protects Pixi on coreruleset.org](https://coreruleset.org/20190909/how-the-crs-protects-the-vulnerable-web-application-pixi-by-owasp-devslop/) 47 | * [CRS as Part of DevOps on coreruleset.org](https://coreruleset.org/20180619/the-core-rule-set-as-part-of-devops-ci-pipeline/) 48 | 49 | Also see Testcafe tests of known vulnerabilities in Pixi [in this branch](https://github.com/DevSlop/pixi-crs/tree/test-pixi-vulnerabilities). 50 | 51 | ### Description of first CRS RP Docker Container 52 | * [Description of the first CRS RP (now the changes are integrated into the official OWASP CRS Container)](https://coreruleset.org/20181212/core-rule-set-docker-image/) 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf: -------------------------------------------------------------------------------- 1 | # Rule 920350 (msg: Host header is a numeric IP address) triggers 2 | # because we use IP addresses instead of hostnames in testcafe tests. 3 | # This rule must not be excluded in production! 4 | SecRuleRemoveById 920350 5 | 6 | # Rule 942100 (msg: SQL Injection Attack Detected via libinjection) trigger, 7 | # Rule 942440 (msg: SQL Comment Sequence Detected) and 8 | # Rule 942450 (msg: SQL Hex Encoding Identified) and 9 | # because of random characters in the session cookie. 10 | SecRuleUpdateTargetById 942100 !REQUEST_COOKIES:session 11 | SecRuleUpdateTargetById 942440 !REQUEST_COOKIES:session 12 | SecRuleUpdateTargetById 942450 !REQUEST_COOKIES:session 13 | 14 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Please report (suspected) security vulnerabilities to owasp.devslop@gmail.com. 6 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Azure DevOps azure-pipelines.yml 2 | 3 | trigger: 4 | # Branch to trigger 5 | - master 6 | 7 | stages: 8 | 9 | # Start the OWASP ModSecurity Core Rule Set and Pixi with its DB with docker-compose 10 | # OWASP ModSecurity Core Rule Set Container (Apache Reverse Proxy) 11 | # owasp/modsecurity-crs 12 | # See https://coreruleset.org/ 13 | # ModSecurity Tuning: 14 | # See https://www.netnea.com/cms/apache-tutorial-8_handling-false-positives-modsecurity-core-rule-set/ 15 | - stage: StartContainersAndTests 16 | jobs: 17 | - job: BuildJob 18 | steps: 19 | # Debugging 20 | # - script: pwd 21 | # - script: ls 22 | - task: DockerCompose@0 23 | displayName: Start Pixi and CRS 24 | inputs: 25 | containerregistrytype: 'Container Registry' 26 | dockerComposeFile: '**/docker-compose.yaml' 27 | action: 'Run a Docker Compose command' 28 | dockerComposeFileArgs: | 29 | CRSPORTHTTP=8080 30 | BACKEND=http://app:8000 31 | ERRORLOG=/var/log/apache2/error.log 32 | dockerComposeCommand: 'up -d' 33 | 34 | # Application Tests with Testcafe 35 | # skip-js-errors because of: Uncaught Error: Bootstrap tooltips require Tether 36 | # Another way: https://devexpress.github.io/testcafe/documentation/continuous-integration/azure-devops.html 37 | # Debugging 38 | #- script: docker ps 39 | - script: docker run --volume /home/vsts/work/1/s/testcafe/tests_container_ip/:/tests testcafe/testcafe --skip-js-errors 'chromium:headless --no-sandbox' 40 | displayName: Run Testcafe Tests without and with CRS 41 | 42 | # Show Full error.log 43 | - script: docker exec crs cat /var/log/apache2/error.log 44 | displayName: Show ModSecurity logs 45 | 46 | # ModSecurity Log Analysis: 47 | # Fail if ModSecurity log is not empty 48 | # Show ModSecurity logs of Testcafe Tests 49 | # If not empty -> Repair your application OR 50 | # -> ModSecurity Tuning: 51 | # See https://www.netnea.com/cms/apache-tutorial-8_handling-false-positives-modsecurity-core-rule-set/ OR 52 | # -> GitHub issue: https://github.com/SpiderLabs/owasp-modsecurity-crs 53 | - script: if docker exec crs cat /var/log/apache2/error.log | grep ModSecurity | grep -vi MyEvilWAFTest | grep -v 949110 | grep -v 980130 | grep msg; then echo "False Positive Found! Aborting!" && exit 1 ; else echo "ModSecurity Logs empty. This is good!"; fi 54 | displayName: Fail if ModSecurity logs are not empty 55 | 56 | # Fail if ModSecurity log does not contain WAF Test String "MyEvilWAFTest" 57 | # That means CRS is not working properly or test was aborted. 58 | - script: if docker exec crs cat /var/log/apache2/error.log | grep ModSecurity | grep MyEvilWAFTest; then echo "WAF Test String Found. This is good!"; else echo "WAF Test String not Found! Aborting!" && exit 1; fi 59 | displayName: Fail if WAF Test String is missing in ModSecurity logs 60 | 61 | -------------------------------------------------------------------------------- /buildspec.yml: -------------------------------------------------------------------------------- 1 | # AWS buildspec.yml 2 | # 3 | version: 0.2 4 | phases: 5 | install: 6 | runtime-versions: 7 | docker: 19 8 | 9 | build: 10 | commands: 11 | # We install testcafe. We don't run it in Docker, because volumes can not be mounted! 12 | # They probably could be mounted in a docker-compose, but let's run testcafe another way... 13 | - npm install -g testcafe 14 | # Start the OWASP ModSecurity Core Rule Set and Pixi with its DB with docker-compose 15 | # OWASP ModSecurity Core Rule Set Container (Apache Reverse Proxy) 16 | # owasp/modsecurity-crs 17 | # See https://coreruleset.org/ 18 | # ModSecurity Tuning: 19 | # See https://www.netnea.com/cms/apache-tutorial-8_handling-false-positives-modsecurity-core-rule-set/ 20 | 21 | # We have to install a higher version of docker-compose manually 22 | # so that --env-file is available 23 | # Unfortunately -e does not work, even with a higer docker-compose version 24 | - curl -L https://github.com/docker/compose/releases/download/1.26.2/docker-compose-`uname -s`-`uname -m` > ~/docker-compose 25 | - chmod +x ~/docker-compose 26 | - mv ~/docker-compose /usr/local/bin/docker-compose 27 | - docker-compose -v 28 | - cat compose-aws.env 29 | - docker-compose --env-file compose-aws.env up -d 30 | 31 | # Application Tests with Testcafe 32 | # skip-js-errors because of: Uncaught Error: Bootstrap tooltips require Tether 33 | - testcafe "chrome:headless" testcafe/tests_localhost/test.js --skip-js-errors 34 | # Application Tests with CRS with Testcafe 35 | - testcafe "chrome:headless" testcafe/tests_localhost/testcrs.js --skip-js-errors 36 | # WAF Tests with malicous request to test WAF itself 37 | - testcafe "chrome:headless" testcafe/tests_localhost/testwaf.js --skip-js-errors 38 | 39 | post_build: 40 | commands: 41 | # Fail if ModSecurity log is not empty 42 | # Show ModSecurity logs of Testcafe Tests 43 | - docker exec crs cat /var/log/apache2/error.log | grep ModSecurity | grep error | grep -vi "MyEvilWAFTest" | grep -v "949110" | grep -vi "980130" && echo "False Positive Found. Check Logs. Aborting!" && exit 1 || exit 0 44 | 45 | # If not empty -> Repair your application OR 46 | # -> ModSecurity Tuning: 47 | # See https://www.netnea.com/cms/apache-tutorial-8_handling-false-positives-modsecurity-core-rule-set/ OR 48 | # -> GitHub issue: https://github.com/SpiderLabs/owasp-modsecurity-crs 49 | 50 | # Fail if ModSecurity log does not contain WAF Test String "MyEvilWAFTest" 51 | # That means CRS is not working properly or test was aborted. 52 | - docker exec crs cat /var/log/apache2/error.log | grep -q MyEvilWAFTest 53 | 54 | # Show ModSecurity Full Logs: 55 | - docker exec crs cat /var/log/apache2/error.log | grep ModSecurity | grep msg 56 | 57 | 58 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # Google Cloud cloudbuild.yaml 2 | # 3 | steps: 4 | # Start the OWASP ModSecurity Core Rule Set and Pixi with its DB with docker-compose 5 | # OWASP ModSecurity Core Rule Set Container (Apache Reverse Proxy) 6 | # owasp/modsecurity-crs 7 | # See https://coreruleset.org/ 8 | # ModSecurity Tuning: 9 | # See https://www.netnea.com/cms/apache-tutorial-8_handling-false-positives-modsecurity-core-rule-set/ 10 | - id: 'Starting Pixi and CRS with docker-compose up' 11 | name: 'docker/compose:1.29.2' 12 | args: ['--env-file', '/workspace/compose-gcp.env', 'up', '-d'] 13 | 14 | # Debugging possibilities 15 | #- name: 'ubuntu' 16 | # args: [ "touch", "foo" ] 17 | #- name: 'ubuntu' 18 | # args: [ "ls", "-l", "/workspace/testcafe/tests_container_ip" ] 19 | #- name: 'ubuntu' 20 | # args: [ "pwd" ] 21 | # Debugging with curl 22 | #- name: 'curlimages/curl:7.69.0' 23 | # args: [ "-v", "http://172.17.0.1:8000/register"] 24 | #- name: 'curlimages/curl:7.69.0' 25 | # args: [ "-v", "http://172.17.0.1:8080/register"] 26 | 27 | # Application Tests with Testcafe 28 | # skip-js-errors because of: Uncaught Error: Bootstrap tooltips require Tether 29 | - id: 'Run Testcafe Tests: Pixi without and with CRS' 30 | name: 'gcr.io/cloud-builders/docker' 31 | args: [ "run", "--volume", "/workspace/testcafe/tests_container_ip:/tests", "--rm", "testcafe/testcafe", "chromium:headless --no-sandbox", "--skip-js-errors" ] 32 | 33 | # Copy ModSecurity Logs: 34 | - id: 'Copy ModSecurity logs' 35 | name: 'gcr.io/cloud-builders/docker' 36 | # args: [ "exec", "crs", "cat /var/log/apache2/error.log | grep ModSecurity" ] 37 | args: [ "cp", "crs:/var/log/apache2/error.log", "/workspace/error.log" ] 38 | 39 | # Show ModSecurity Logs 40 | - id: 'Show ModSecurity logs' 41 | name: 'gcr.io/cloud-builders/gcloud' 42 | entrypoint: "bash" 43 | args: 44 | - "-c" 45 | - | 46 | cat /workspace/error.log 47 | 48 | 49 | # ModSecurity Log Analysis: 50 | # Fail if ModSecurity log does not contain WAF Test String "MyEvilWAFTest" 51 | # That means CRS is not working properly or test was aborted. 52 | 53 | - id: 'Fail if ModSecurity log does not contain WAF Test String' 54 | name: 'gcr.io/cloud-builders/gcloud' 55 | entrypoint: "bash" 56 | args: 57 | - "-c" 58 | - | 59 | cat /workspace/error.log | grep -q MyEvilWAFTest 60 | 61 | # Fail if ModSecurity log is not empty 62 | # Show ModSecurity logs of Testcafe Tests 63 | # If not empty -> Repair your application OR 64 | # -> ModSecurity Tuning: 65 | # See https://www.netnea.com/cms/apache-tutorial-8_handling-false-positives-modsecurity-core-rule-set/ OR 66 | # -> GitHub issue: https://github.com/SpiderLabs/owasp-modsecurity-crs 67 | - id: 'Fail if ModSecurity log is not empty' 68 | name: 'gcr.io/cloud-builders/gcloud' 69 | entrypoint: "bash" 70 | args: 71 | - "-c" 72 | - | 73 | cat /workspace/error.log | grep ModSecurity | grep error | grep -vi MyEvilWAFTest | grep -v 949110 | grep -v 980130 && exit 1 || exit 0 74 | 75 | #- id: 'Fail if ModSecurity log is not empty' 76 | # name: 'gcr.io/cloud-builders/docker' 77 | # args: [ 'exec', 'crs', 'cat /var/log/apache2/error.log | grep ModSecurity | grep error | grep -vi "MyEvilWAFTest" | grep -v "949110" | grep -vi "980130" && exit 1 || exit 0' ] 78 | 79 | 80 | # Debugging docker 81 | #- id: 'Docker' 82 | # name: 'gcr.io/cloud-builders/docker' 83 | # args: [ "ps" ] 84 | -------------------------------------------------------------------------------- /compose-aws.env: -------------------------------------------------------------------------------- 1 | CRSPORTHTTP=80 2 | BACKEND=http://app:8000 3 | ERRORLOG=/var/log/apache2/error.log 4 | -------------------------------------------------------------------------------- /compose-gcp.env: -------------------------------------------------------------------------------- 1 | CRSPORTHTTP=8080 2 | BACKEND=http://app:8000 3 | ERRORLOG=/var/log/apache2/error.log 4 | -------------------------------------------------------------------------------- /compose-local.env: -------------------------------------------------------------------------------- 1 | CRSPORTHTTP=8080 2 | BACKEND=http://app:8000 3 | ERRORLOG=/dev/stdout 4 | -------------------------------------------------------------------------------- /docker-compose-without-crs.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | # !!! We can not start crs in docker-compose because volumes are not supported in CircleCI!!! 6 | 7 | # Comment in crs part if you want to add a WAF in front of Pixi: 8 | # crs: 9 | # image: owasp/modsecurity-crs:3.3-apache 10 | # container_name: crs 11 | # ports: 12 | # - "8080:80" 13 | # - "8443:443" 14 | # 15 | # environment: 16 | # # Application Backend listens on port 8000 (IP: REPLACEME, but do not use localhost!) 17 | # - BACKEND=http://app:8000 18 | # # Paranoia Level 19 | # - PARANOIA=2 20 | # # Inbound and Outbound Anomaly Score Threshold 21 | # - ANOMALYIN=1 22 | # - ANOMALYOUT=1 23 | # # Executing Paranoia Level 24 | # # - EXECUTING_PARANOIA=2 25 | # 26 | # # Various CRS Variables with Default Values 27 | # #- ENFORCE_BODYPROC_URLENCODED=1 28 | # - ALLOWED_METHODS=GET HEAD POST OPTIONS PUT 29 | # #- ALLOWED_REQUEST_CONTENT_TYPE=application/x-www-form-urlencoded|multipart/form-data|text/xml|application/xml|application/soap+xml|application/x-amf|application/json|application/octet-stream|text/plain 30 | # #- ALLOWED_REQUEST_CONTENT_TYPE_CHARSET=utf-8|iso-8859-1|iso-8859-15|windows-1252 31 | # #- ALLOWED_HTTP_VERSIONS=HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0 32 | # #- RESTRICTED_EXTENSIONS=.asa/ .asax/ .ascx/ .axd/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .resources/ .resx/ .sql/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/ 33 | # #- RESTRICTED_HEADERS=/proxy/ /lock-token/ /content-range/ /translate/ /if/ 34 | # #- STATIC_EXTENSIONS=/.jpg/ /.jpeg/ /.png/ /.gif/ /.js/ /.css/ /.ico/ /.svg/ /.webp/ 35 | # 36 | # # CRS Variables with Default Value unlimited: 37 | # #- MAX_NUM_ARGS=255 38 | # #- ARG_NAME_LENGTH=100 39 | # #- ARG_LENGTH=400 40 | # #- TOTAL_ARG_LENGTH=64000 41 | # #- MAX_FILE_SIZE=1048576 42 | # #- COMBINED_FILE_SIZES=1048576 43 | # 44 | # # Volumes for ModSecurity Tuning when done with volumes: 45 | # volumes: 46 | # #- ./REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf:/etc/modsecurity.d/owasp-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf 47 | # - ./RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf:/etc/modsecurity.d/owasp-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf 48 | # 49 | 50 | db: 51 | image: deadrobots/pixi:datastore 52 | container_name: pixidb 53 | ports: 54 | - "27017:27017" 55 | - "28017:28017" 56 | 57 | app: 58 | image: deadrobots/pixi:app 59 | container_name: pixi 60 | ports: 61 | - "8000:8000" 62 | - "8090:8090" 63 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | # Comment in crs part if you want to add a WAF in front of Pixi: 6 | crs: 7 | image: owasp/modsecurity-crs:3.3-apache 8 | container_name: crs 9 | ports: 10 | # 443 is already in use 11 | - "${CRSPORTHTTP}:80" 12 | #- "8443:443" NOT USED FOR TESTS 13 | 14 | environment: 15 | # Application Backend listens on port 8000 (IP: REPLACEME, but do not use localhost!) 16 | - BACKEND=${BACKEND} 17 | # Paranoia Level 18 | - PARANOIA=2 19 | # Inbound and Outbound Anomaly Score Threshold 20 | # - ANOMALY_INBOUND=5 21 | # - ANOMALY_OUTBOUND=4 22 | # Executing Paranoia Level 23 | # - EXECUTING_PARANOIA=2 24 | # Write errorlog to filesystem instead of stdout for later analysis 25 | - ERRORLOG=${ERRORLOG} 26 | 27 | # Various CRS Variables with Default Values 28 | #- ENFORCE_BODYPROC_URLENCODED=1 29 | - ALLOWED_METHODS=GET HEAD POST OPTIONS PUT 30 | #- ALLOWED_REQUEST_CONTENT_TYPE=|application/x-www-form-urlencoded| |multipart/form-data| |multipart/related| |text/xml| |application/xml| |application/soap+xml| |application/x-amf| |application/json| |application/cloudevents+json| |application/cloudevents-batch+json| |application/octet-stream| |application/csp-report| |application/xss-auditor-report| |text/plain| 31 | #- ALLOWED_REQUEST_CONTENT_TYPE_CHARSET=utf-8|iso-8859-1|iso-8859-15|windows-1252 32 | #- ALLOWED_HTTP_VERSIONS=HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0 33 | #- RESTRICTED_EXTENSIONS=.asa/ .asax/ .ascx/ .axd/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .resources/ .resx/ .sql/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/ 34 | #- RESTRICTED_HEADERS=/proxy/ /lock-token/ /content-range/ /translate/ /if/ 35 | #- STATIC_EXTENSIONS=/.jpg/ /.jpeg/ /.png/ /.gif/ /.js/ /.css/ /.ico/ /.svg/ /.webp/ 36 | 37 | # CRS Variables with Default Value unlimited: 38 | #- MAX_NUM_ARGS=255 39 | #- ARG_NAME_LENGTH=100 40 | #- ARG_LENGTH=400 41 | #- TOTAL_ARG_LENGTH=64000 42 | #- MAX_FILE_SIZE=1048576 43 | #- COMBINED_FILE_SIZES=1048576 44 | 45 | # Volumes for ModSecurity Tuning when done with volumes: 46 | volumes: 47 | #- ./REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf:/etc/modsecurity.d/owasp-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf 48 | - ./RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf:/etc/modsecurity.d/owasp-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf 49 | 50 | 51 | db: 52 | image: deadrobots/pixi:datastore 53 | container_name: pixidb 54 | ports: 55 | - "27017:27017" 56 | - "28017:28017" 57 | 58 | app: 59 | image: deadrobots/pixi:app 60 | container_name: pixi 61 | ports: 62 | - "8000:8000" 63 | - "8090:8090" 64 | 65 | -------------------------------------------------------------------------------- /img/AWS/AWS_CodeBuild_Logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/AWS/AWS_CodeBuild_Logs.png -------------------------------------------------------------------------------- /img/AWS/AWS_Pipeline_Output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/AWS/AWS_Pipeline_Output.png -------------------------------------------------------------------------------- /img/AWS/AWS_PixiCRS_BuildProject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/AWS/AWS_PixiCRS_BuildProject.png -------------------------------------------------------------------------------- /img/AWS/AWS_Testcafe_Output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/AWS/AWS_Testcafe_Output.png -------------------------------------------------------------------------------- /img/AWS/AWS_Title_Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/AWS/AWS_Title_Image.png -------------------------------------------------------------------------------- /img/AzureDevOps/Azure_Pipeline_Output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/AzureDevOps/Azure_Pipeline_Output.png -------------------------------------------------------------------------------- /img/AzureDevOps/Azure_Testcafe_Output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/AzureDevOps/Azure_Testcafe_Output.png -------------------------------------------------------------------------------- /img/AzureDevOps/Azure_Title_Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/AzureDevOps/Azure_Title_Image.png -------------------------------------------------------------------------------- /img/AzureDevOps/azure-1-new-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/AzureDevOps/azure-1-new-project.png -------------------------------------------------------------------------------- /img/AzureDevOps/azure-2-create-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/AzureDevOps/azure-2-create-pipeline.png -------------------------------------------------------------------------------- /img/AzureDevOps/azure-3-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/AzureDevOps/azure-3-github.png -------------------------------------------------------------------------------- /img/AzureDevOps/azure-4-select-repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/AzureDevOps/azure-4-select-repo.png -------------------------------------------------------------------------------- /img/AzureDevOps/azure-5-review-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/AzureDevOps/azure-5-review-pipeline.png -------------------------------------------------------------------------------- /img/CircleCI/CircleCI_Pipeline_Output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/CircleCI/CircleCI_Pipeline_Output.png -------------------------------------------------------------------------------- /img/GCP/GCP_Testcafe_Output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/GCP/GCP_Testcafe_Output.png -------------------------------------------------------------------------------- /img/GCP/GCP_Title_Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/GCP/GCP_Title_Image.png -------------------------------------------------------------------------------- /img/GitHub_Actions/GHA_Pipeline_Output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/GitHub_Actions/GHA_Pipeline_Output.png -------------------------------------------------------------------------------- /img/GitHub_Actions/GHA_Testcafe_Output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DevSlop/pixi-crs/4ed481c93e4d1066487db0af1f43a05b20d504e7/img/GitHub_Actions/GHA_Testcafe_Output.png -------------------------------------------------------------------------------- /testcafe/tests_container_ip/test.js: -------------------------------------------------------------------------------- 1 | // Normal Pixi Application Tests against Pixi directly for CI Pipeline 2 | import { Selector } from 'testcafe'; 3 | 4 | fixture`Getting Started` 5 | .page `http://172.17.0.1:8000/register`; 6 | 7 | test('Register User', async t => { 8 | await t 9 | .typeText('input#email', 'testuser@pixi.owasp') 10 | .typeText('input#password', 'testpw') 11 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 12 | }); 13 | 14 | test('Login User', async t => { 15 | await t 16 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 17 | .typeText('input#user', 'testuser@pixi.owasp') 18 | .typeText('input#pass', 'testpw') 19 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 20 | }); 21 | 22 | test('Search String in Search Box', async t => { 23 | await t 24 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 25 | .typeText('input#user', 'testuser@pixi.owasp') 26 | .typeText('input#pass', 'testpw') 27 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 28 | //After login we search for Lunch 29 | .typeText('#search_query', 'Lunch') 30 | .pressKey('enter') 31 | }); 32 | 33 | test('Click About', async t => { 34 | await t 35 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 36 | .typeText('input#user', 'testuser@pixi.owasp') 37 | .typeText('input#pass', 'testpw') 38 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 39 | //After login we click About 40 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(1) > a') 41 | //.expect('body > div > div:nth-child(12) > div > div.card-deck') 42 | }); 43 | 44 | test('Click My Profile and change Name', async t => { 45 | await t 46 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 47 | .typeText('input#user', 'testuser@pixi.owasp') 48 | .typeText('input#pass', 'testpw') 49 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 50 | //After login we click My Profile 51 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(3) > a') 52 | //After clicking My Profile we change our name 53 | .typeText('input#password', 'testpw') 54 | .typeText('input#name', 'testuser') 55 | .pressKey('enter') 56 | }); 57 | 58 | test('Logout User', async t => { 59 | await t 60 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 61 | .typeText('input#user', 'testuser@pixi.owasp') 62 | .typeText('input#pass', 'testpw') 63 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 64 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(4) > a') 65 | }); 66 | -------------------------------------------------------------------------------- /testcafe/tests_container_ip/testcrs.js: -------------------------------------------------------------------------------- 1 | // Normal Pixi Application Tests against CRS for CI Pipeline 2 | import { Selector } from 'testcafe'; 3 | 4 | fixture`Getting Started` 5 | .page `http://172.17.0.1:8080/register`; 6 | 7 | test('Register User WITH CRS', async t => { 8 | await t 9 | .typeText('input#email', 'testuser@pixi.owasp') 10 | .typeText('input#password', 'testpw') 11 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 12 | }); 13 | 14 | test('Login User WITH CRS', async t => { 15 | await t 16 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 17 | .typeText('input#user', 'testuser@pixi.owasp') 18 | .typeText('input#pass', 'testpw') 19 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 20 | }); 21 | 22 | test('Search String in Search Box WITH CRS', async t => { 23 | await t 24 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 25 | .typeText('input#user', 'testuser@pixi.owasp') 26 | .typeText('input#pass', 'testpw') 27 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 28 | //Evil demo, let step fail 29 | //.typeText('#search_query', '') 30 | .pressKey('enter') 31 | }); 32 | 33 | test('Click About WITH CRS', async t => { 34 | await t 35 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 36 | .typeText('input#user', 'testuser@pixi.owasp') 37 | .typeText('input#pass', 'testpw') 38 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 39 | //After login we click About 40 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(1) > a') 41 | }); 42 | 43 | test('Click My Profile and change Name WITH CRS', async t => { 44 | await t 45 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 46 | .typeText('input#user', 'testuser@pixi.owasp') 47 | .typeText('input#pass', 'testpw') 48 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 49 | //After login we click My Profile 50 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(3) > a') 51 | //After clicking My Profile we change our name 52 | .typeText('input#password', 'testpw') 53 | .typeText('input#name', 'testuser') 54 | .pressKey('enter') 55 | }); 56 | 57 | test('Logout User WITH CRS', async t => { 58 | await t 59 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 60 | .typeText('input#user', 'testuser@pixi.owasp') 61 | .typeText('input#pass', 'testpw') 62 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 63 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(4) > a') 64 | }); 65 | 66 | -------------------------------------------------------------------------------- /testcafe/tests_container_ip/testwaf.js: -------------------------------------------------------------------------------- 1 | // One malicious Pixi Application Test against CRS for CI Pipeline to test the WAF itself 2 | import { Selector } from 'testcafe'; 3 | 4 | fixture`Getting Started` 5 | .page `http://172.17.0.1:8080/login`; 6 | 7 | test('WAF Test with malicious string', async t => { 8 | await t 9 | .typeText('input#user', 'testuser@pixi.owasp') 10 | .typeText('input#pass', '') 11 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 12 | }); 13 | -------------------------------------------------------------------------------- /testcafe/tests_container_ip2/mongodb.js: -------------------------------------------------------------------------------- 1 | // Evil MongoDB requests against Pixi directly for CI Pipeline (Demo) 2 | import { Selector } from 'testcafe'; 3 | 4 | fixture`Getting Started` 5 | .page `http://172.17.0.1:8000/register`; 6 | 7 | test('Register User', async t => { 8 | await t 9 | .typeText('input#email', 'testuser@pixi.owasp') 10 | .typeText('input#password', 'testpw') 11 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 12 | //After registration the search pixi field should be there 13 | //.expect('#search_query') 14 | }); 15 | 16 | test('Login and Search', async t => { 17 | await t 18 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 19 | .typeText('input#user', 'testuser@pixi.owasp') 20 | .typeText('input#pass', 'testpw') 21 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 22 | //After login we search for Lunch 23 | .typeText('#search_query', 'true, $where: \'1 == 1\'') 24 | .pressKey('enter') 25 | .typeText('#search_query', ', $where: \'1 == 1\'') 26 | .pressKey('enter') 27 | .typeText('#search_query', '$where: \'1 == 1\'') 28 | .pressKey('enter') 29 | .typeText('#search_query', '\', $where: \'1 == 1\'') 30 | .pressKey('enter') 31 | .typeText('#search_query', '1, $where: \'1 == 1\'') 32 | .pressKey('enter') 33 | .typeText('#search_query', '{ $ne: 1 }') 34 | .pressKey('enter') 35 | .typeText('#search_query', '\', $or: [ {}, { \'a\':\'a') 36 | .pressKey('enter') 37 | .typeText('#search_query', '\' } ], $comment:\'successful MongoDB injection\'') 38 | .pressKey('enter') 39 | .typeText('#search_query', 'db.injection.insert({success:1});') 40 | .pressKey('enter') 41 | .typeText('#search_query', 'db.injection.insert({success:1});return 1;db.stores.mapReduce(function() { { emit(1,1') 42 | .pressKey('enter') 43 | .typeText('#search_query', '|| 1==1') 44 | .pressKey('enter') 45 | .typeText('#search_query', '\' && this.password.match(/.*/)//+%00') 46 | .pressKey('enter') 47 | .typeText('#search_query', '\' && this.passwordzz.match(/.*/)//+%00') 48 | .pressKey('enter') 49 | .typeText('#search_query', '\'%20%26%26%20this.password.match(/.*/)//+%00') 50 | .pressKey('enter') 51 | .typeText('#search_query', '\'%20%26%26%20this.passwordzz.match(/.*/)//+%00') 52 | .pressKey('enter') 53 | .typeText('#search_query', '{$gt: \'\'}') 54 | .pressKey('enter') 55 | .typeText('#search_query', '[$ne]=1') 56 | .pressKey('enter') 57 | .typeText('#search_query', '\';sleep(5000);') 58 | .pressKey('enter') 59 | .typeText('#search_query', '\';it=new%20Date();do{pt=new%20Date();}while(pt-it<5000);') 60 | .pressKey('enter') 61 | //.expect('body > div > div:nth-child(12) > div > div.card-deck') 62 | }); 63 | -------------------------------------------------------------------------------- /testcafe/tests_container_ip2/mongodbcrs.js: -------------------------------------------------------------------------------- 1 | // Evil MongoDB requests against CRS for CI Pipeline (Demo) 2 | import { Selector } from 'testcafe'; 3 | 4 | fixture`Getting Started` 5 | .page `http://172.17.0.2/register`; 6 | 7 | test('Register User WITH CRS', async t => { 8 | await t 9 | .typeText('input#email', 'testuser@pixi.owasp') 10 | .typeText('input#password', 'testpw') 11 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 12 | //After registration the search pixi field should be there 13 | //.expect('#search_query') 14 | }); 15 | 16 | test('Login and Search', async t => { 17 | await t 18 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 19 | .typeText('input#user', 'testuser@pixi.owasp') 20 | .typeText('input#pass', 'testpw') 21 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 22 | //After login we search for Lunch 23 | .typeText('#search_query', '[$exists]') 24 | .pressKey('enter') 25 | .typeText('#search_query', 'true, $where: \'1 == 1\'') 26 | .pressKey('enter') 27 | .typeText('#search_query', ', $where: \'1 == 1\'') 28 | .pressKey('enter') 29 | .typeText('#search_query', '$where: \'1 == 1\'') 30 | .pressKey('enter') 31 | .typeText('#search_query', '\', $where: \'1 == 1\'') 32 | .pressKey('enter') 33 | .typeText('#search_query', '1, $where: \'1 == 1\'') 34 | .pressKey('enter') 35 | .typeText('#search_query', '{ $ne: 1 }') 36 | .pressKey('enter') 37 | .typeText('#search_query', '\', $or: [ {}, { \'a\':\'a') 38 | .pressKey('enter') 39 | .typeText('#search_query', '\' } ], $comment:\'successful MongoDB injection\'') 40 | .pressKey('enter') 41 | .typeText('#search_query', 'db.injection.insert({success:1});') 42 | .pressKey('enter') 43 | .typeText('#search_query', 'db.injection.insert({success:1});return 1;db.stores.mapReduce(function() { { emit(1,1') 44 | .pressKey('enter') 45 | .typeText('#search_query', '|| 1==1') 46 | .pressKey('enter') 47 | .typeText('#search_query', '\' && this.password.match(/.*/)//+%00') 48 | .pressKey('enter') 49 | .typeText('#search_query', '\' && this.passwordzz.match(/.*/)//+%00') 50 | .pressKey('enter') 51 | .typeText('#search_query', '\'%20%26%26%20this.password.match(/.*/)//+%00') 52 | .pressKey('enter') 53 | .typeText('#search_query', '\'%20%26%26%20this.passwordzz.match(/.*/)//+%00') 54 | .pressKey('enter') 55 | .typeText('#search_query', '{$gt: \'\'}') 56 | .pressKey('enter') 57 | .typeText('#search_query', '[$ne]=1') 58 | .pressKey('enter') 59 | .typeText('#search_query', '\';sleep(5000);') 60 | .pressKey('enter') 61 | .typeText('#search_query', '\';it=new%20Date();do{pt=new%20Date();}while(pt-it<5000);') 62 | .pressKey('enter') 63 | //.expect('body > div > div:nth-child(12) > div > div.card-deck') 64 | }); 65 | -------------------------------------------------------------------------------- /testcafe/tests_container_ip2/test.js: -------------------------------------------------------------------------------- 1 | // Normal Pixi Application Tests against Pixi directly for CI Pipeline 2 | import { Selector } from 'testcafe'; 3 | 4 | fixture`Getting Started` 5 | .page `http://172.17.0.1:8000/register`; 6 | 7 | test('Register User', async t => { 8 | await t 9 | .typeText('input#email', 'testuser@pixi.owasp') 10 | .typeText('input#password', 'testpw') 11 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 12 | //After registration the search pixi field should be there 13 | //.expect('#search_query') 14 | }); 15 | 16 | test('Login User', async t => { 17 | await t 18 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 19 | .typeText('input#user', 'testuser@pixi.owasp') 20 | .typeText('input#pass', 'testpw') 21 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 22 | }); 23 | 24 | test('Search String in Search Box', async t => { 25 | await t 26 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 27 | .typeText('input#user', 'testuser@pixi.owasp') 28 | .typeText('input#pass', 'testpw') 29 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 30 | //After login we search for Lunch 31 | .typeText('#search_query', 'Lunch') 32 | .pressKey('enter') 33 | //.expect('body > div > div:nth-child(12) > div > div.card-deck') 34 | }); 35 | 36 | test('Click About', async t => { 37 | await t 38 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 39 | .typeText('input#user', 'testuser@pixi.owasp') 40 | .typeText('input#pass', 'testpw') 41 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 42 | //After login we click About 43 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(1) > a') 44 | //.expect('body > div > div:nth-child(12) > div > div.card-deck') 45 | }); 46 | 47 | test('Click My Profile and change Name', async t => { 48 | await t 49 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 50 | .typeText('input#user', 'testuser@pixi.owasp') 51 | .typeText('input#pass', 'testpw') 52 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 53 | //After login we click My Profile 54 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(3) > a') 55 | //After clicking My Profile we change our name 56 | .typeText('input#password', 'testpw') 57 | .typeText('input#name', 'testuser') 58 | .pressKey('enter') 59 | }); 60 | 61 | test('Logout User', async t => { 62 | await t 63 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 64 | .typeText('input#user', 'testuser@pixi.owasp') 65 | .typeText('input#pass', 'testpw') 66 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 67 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(4) > a') 68 | }); 69 | 70 | test('Repeat Login for Video', async t => { 71 | await t 72 | .typeText('input#email', 'testuser@pixi.owasp') 73 | .typeText('input#password', 'testpw') 74 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 75 | //After registration the search pixi field should be there 76 | //.expect('#search_query') 77 | }); 78 | 79 | -------------------------------------------------------------------------------- /testcafe/tests_container_ip2/test_local.js: -------------------------------------------------------------------------------- 1 | // Normal Pixi Application Tests against Pixi directly for CI Pipeline 2 | import { Selector } from 'testcafe'; 3 | 4 | fixture`Getting Started` 5 | .page `http://192.168.1.104:8000/register`; 6 | 7 | test('Register User', async t => { 8 | await t 9 | .typeText('input#email', 'testuser@pixi.owasp') 10 | .typeText('input#password', 'testpw') 11 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 12 | //After registration the search pixi field should be there 13 | //.expect('#search_query') 14 | }); 15 | 16 | test('Login User', async t => { 17 | await t 18 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 19 | .typeText('input#user', 'testuser@pixi.owasp') 20 | .typeText('input#pass', 'testpw') 21 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 22 | }); 23 | 24 | test('Search String in Search Box', async t => { 25 | await t 26 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 27 | .typeText('input#user', 'testuser@pixi.owasp') 28 | .typeText('input#pass', 'testpw') 29 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 30 | //After login we search for Lunch 31 | .typeText('#search_query', 'Lunch') 32 | .pressKey('enter') 33 | //.expect('body > div > div:nth-child(12) > div > div.card-deck') 34 | }); 35 | 36 | test('Click About', async t => { 37 | await t 38 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 39 | .typeText('input#user', 'testuser@pixi.owasp') 40 | .typeText('input#pass', 'testpw') 41 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 42 | //After login we click About 43 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(1) > a') 44 | //.expect('body > div > div:nth-child(12) > div > div.card-deck') 45 | }); 46 | 47 | test('Click My Profile and change Name', async t => { 48 | await t 49 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 50 | .typeText('input#user', 'testuser@pixi.owasp') 51 | .typeText('input#pass', 'testpw') 52 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 53 | //After login we click My Profile 54 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(3) > a') 55 | //After clicking My Profile we change our name 56 | .typeText('input#password', 'testpw') 57 | .typeText('input#name', 'testuser') 58 | .pressKey('enter') 59 | }); 60 | 61 | test('Logout User', async t => { 62 | await t 63 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 64 | .typeText('input#user', 'testuser@pixi.owasp') 65 | .typeText('input#pass', 'testpw') 66 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 67 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(4) > a') 68 | }); 69 | -------------------------------------------------------------------------------- /testcafe/tests_container_ip2/testcrs-match.js: -------------------------------------------------------------------------------- 1 | import { Selector } from 'testcafe'; 2 | 3 | fixture`Getting Started` 4 | .page `http://172.17.0.2/register`; 5 | 6 | test('Register User WITH CRS', async t => { 7 | await t 8 | .typeText('input#email', 'testuser@pixi.owasp') 9 | .typeText('input#password', 'testpw') 10 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 11 | //After registration the search pixi field should be there 12 | //.expect('#search_query') 13 | }); 14 | 15 | test('Login User WITH CRS', async t => { 16 | await t 17 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 18 | .typeText('input#user', 'testuser@pixi.owasp') 19 | .typeText('input#pass', 'testpw') 20 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 21 | .navigateTo('http://172.17.0.2/register?$select=userid') 22 | }); 23 | 24 | test('Search String WITH CRS', async t => { 25 | await t 26 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 27 | .typeText('input#user', 'testuser@pixi.owasp') 28 | .typeText('input#pass', 'testpw') 29 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 30 | //After login we search for Lunch 31 | .typeText('#search_query', 'Lunch') 32 | .pressKey('enter') 33 | //.expect('body > div > div:nth-child(12) > div > div.card-deck') 34 | }); 35 | 36 | test('Logout User WITH CRS', async t => { 37 | await t 38 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 39 | .typeText('input#user', 'testuser@pixi.owasp') 40 | .typeText('input#pass', 'testpw') 41 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 42 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(4) > a') 43 | }); 44 | -------------------------------------------------------------------------------- /testcafe/tests_container_ip2/testcrs.js: -------------------------------------------------------------------------------- 1 | // Normal Pixi Application Tests against CRS for CI Pipeline 2 | import { Selector } from 'testcafe'; 3 | 4 | fixture`Getting Started` 5 | // Use 172.17.0.2 instead of 172.17.0.1 if crs is not started via docker-compose: 6 | .page `http://172.17.0.2/register`; 7 | 8 | test('Register User WITH CRS', async t => { 9 | await t 10 | .typeText('input#email', 'testuser@pixi.owasp') 11 | .typeText('input#password', 'testpw') 12 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 13 | //After registration the search pixi field should be there 14 | //.expect('#search_query') 15 | }); 16 | 17 | test('Login User WITH CRS', async t => { 18 | await t 19 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 20 | .typeText('input#user', 'testuser@pixi.owasp') 21 | .typeText('input#pass', 'testpw') 22 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 23 | }); 24 | 25 | test('Search String in Search Box WITH CRS', async t => { 26 | await t 27 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 28 | .typeText('input#user', 'testuser@pixi.owasp') 29 | .typeText('input#pass', 'testpw') 30 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 31 | //Evil demo, let step fail 32 | //.typeText('#search_query', '') 33 | //.pressKey('enter') 34 | //.expect('body > div > div:nth-child(12) > div > div.card-deck') 35 | }); 36 | 37 | test('Click About WITH CRS', async t => { 38 | await t 39 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 40 | .typeText('input#user', 'testuser@pixi.owasp') 41 | .typeText('input#pass', 'testpw') 42 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 43 | //After login we click About 44 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(1) > a') 45 | //.expect('body > div > div:nth-child(12) > div > div.card-deck') 46 | }); 47 | 48 | test('Click My Profile and change Name WITH CRS', async t => { 49 | await t 50 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 51 | .typeText('input#user', 'testuser@pixi.owasp') 52 | .typeText('input#pass', 'testpw') 53 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 54 | //After login we click My Profile 55 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(3) > a') 56 | //After clicking My Profile we change our name 57 | .typeText('input#password', 'testpw') 58 | .typeText('input#name', 'testuser') 59 | .pressKey('enter') 60 | }); 61 | 62 | test('Logout User WITH CRS', async t => { 63 | await t 64 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 65 | .typeText('input#user', 'testuser@pixi.owasp') 66 | .typeText('input#pass', 'testpw') 67 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 68 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(4) > a') 69 | }); 70 | 71 | -------------------------------------------------------------------------------- /testcafe/tests_container_ip2/testwaf.js: -------------------------------------------------------------------------------- 1 | // One malicious Pixi Application Test against CRS for CI Pipeline to test the WAF itself 2 | import { Selector } from 'testcafe'; 3 | 4 | fixture`Getting Started` 5 | .page `http://172.17.0.2/register`; 6 | 7 | test('Register User WITH CRS', async t => { 8 | await t 9 | .typeText('input#email', 'test@test>') 10 | .typeText('input#password', '') 11 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 12 | //After registration the search pixi field should be there 13 | //.expect('#search_query') 14 | }); 15 | -------------------------------------------------------------------------------- /testcafe/tests_localhost/test.js: -------------------------------------------------------------------------------- 1 | // Normal Pixi Application Tests against Pixi directly for CI Pipeline 2 | import { Selector } from 'testcafe'; 3 | 4 | fixture`Getting Started` 5 | .page `http://localhost:8000/register`; 6 | 7 | test('Register User', async t => { 8 | await t 9 | .typeText('input#email', 'testuser@pixi.owasp') 10 | .typeText('input#password', 'testpw') 11 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 12 | }); 13 | 14 | test('Login User', async t => { 15 | await t 16 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 17 | .typeText('input#user', 'testuser@pixi.owasp') 18 | .typeText('input#pass', 'testpw') 19 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 20 | }); 21 | 22 | test('Search String in Search Box', async t => { 23 | await t 24 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 25 | .typeText('input#user', 'testuser@pixi.owasp') 26 | .typeText('input#pass', 'testpw') 27 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 28 | //After login we search for Lunch 29 | .typeText('#search_query', 'Lunch') 30 | .pressKey('enter') 31 | }); 32 | 33 | test('Click About', async t => { 34 | await t 35 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 36 | .typeText('input#user', 'testuser@pixi.owasp') 37 | .typeText('input#pass', 'testpw') 38 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 39 | //After login we click About 40 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(1) > a') 41 | //.expect('body > div > div:nth-child(12) > div > div.card-deck') 42 | }); 43 | 44 | test('Click My Profile and change Name', async t => { 45 | await t 46 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 47 | .typeText('input#user', 'testuser@pixi.owasp') 48 | .typeText('input#pass', 'testpw') 49 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 50 | //After login we click My Profile 51 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(3) > a') 52 | //After clicking My Profile we change our name 53 | .typeText('input#password', 'testpw') 54 | .typeText('input#name', 'testuser') 55 | .pressKey('enter') 56 | }); 57 | 58 | test('Logout User', async t => { 59 | await t 60 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 61 | .typeText('input#user', 'testuser@pixi.owasp') 62 | .typeText('input#pass', 'testpw') 63 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 64 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(4) > a') 65 | }); 66 | -------------------------------------------------------------------------------- /testcafe/tests_localhost/testcrs.js: -------------------------------------------------------------------------------- 1 | // Normal Pixi Application Tests against CRS for CI Pipeline 2 | import { Selector } from 'testcafe'; 3 | 4 | fixture`Getting Started` 5 | .page `http://localhost/register`; 6 | 7 | test('Register User WITH CRS', async t => { 8 | await t 9 | .typeText('input#email', 'testuser@pixi.owasp') 10 | .typeText('input#password', 'testpw') 11 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 12 | }); 13 | 14 | test('Login User WITH CRS', async t => { 15 | await t 16 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 17 | .typeText('input#user', 'testuser@pixi.owasp') 18 | .typeText('input#pass', 'testpw') 19 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 20 | }); 21 | 22 | test('Search String in Search Box WITH CRS', async t => { 23 | await t 24 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 25 | .typeText('input#user', 'testuser@pixi.owasp') 26 | .typeText('input#pass', 'testpw') 27 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 28 | //Evil demo, let step fail 29 | //.typeText('#search_query', '') 30 | .pressKey('enter') 31 | }); 32 | 33 | test('Click About WITH CRS', async t => { 34 | await t 35 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 36 | .typeText('input#user', 'testuser@pixi.owasp') 37 | .typeText('input#pass', 'testpw') 38 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 39 | //After login we click About 40 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(1) > a') 41 | }); 42 | 43 | test('Click My Profile and change Name WITH CRS', async t => { 44 | await t 45 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 46 | .typeText('input#user', 'testuser@pixi.owasp') 47 | .typeText('input#pass', 'testpw') 48 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 49 | //After login we click My Profile 50 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(3) > a') 51 | //After clicking My Profile we change our name 52 | .typeText('input#password', 'testpw') 53 | .typeText('input#name', 'testuser') 54 | .pressKey('enter') 55 | }); 56 | 57 | test('Logout User WITH CRS', async t => { 58 | await t 59 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-4 > a:nth-child(3) > button') 60 | .typeText('input#user', 'testuser@pixi.owasp') 61 | .typeText('input#pass', 'testpw') 62 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 63 | .click('body > div > div:nth-child(1) > div.col-md-7 > ul > li:nth-child(4) > a') 64 | }); 65 | 66 | -------------------------------------------------------------------------------- /testcafe/tests_localhost/testwaf.js: -------------------------------------------------------------------------------- 1 | // One malicious Pixi Application Test against CRS for CI Pipeline to test the WAF itself 2 | import { Selector } from 'testcafe'; 3 | 4 | fixture`Getting Started` 5 | .page `http://localhost/login`; 6 | 7 | test('WAF Test with malicious string', async t => { 8 | await t 9 | .typeText('input#user', 'testuser@pixi.owasp') 10 | .typeText('input#pass', '') 11 | .click('body > div.jumbotron.jumbotron-fluid > div > div:nth-child(5) > div.col-8 > form > button') 12 | }); 13 | -------------------------------------------------------------------------------- /zap/zap-api.conf: -------------------------------------------------------------------------------- 1 | # zap-api-scan rule configuration file 2 | # Change WARN to IGNORE to ignore rule or FAIL to fail if rule matches 3 | # Active scan rules set to IGNORE will not be run which will speed up the scan 4 | # Only the rule identifiers are used - the names are just for info 5 | # You can add your own messages to each rule by appending them after a tab on each line. 6 | 0 WARN (Directory Browsing - Active/release) 7 | 10010 WARN (Cookie No HttpOnly Flag - Passive/release) 8 | 10011 WARN (Cookie Without Secure Flag - Passive/release) 9 | 10012 WARN (Password Autocomplete in Browser - Passive/release) 10 | 10015 WARN (Incomplete or No Cache-control and Pragma HTTP Header Set - Passive/release) 11 | 10016 WARN (Web Browser XSS Protection Not Enabled - Passive/release) 12 | 10017 WARN (Cross-Domain JavaScript Source File Inclusion - Passive/release) 13 | 10019 WARN (Content-Type Header Missing - Passive/release) 14 | 10020 WARN (X-Frame-Options Header Scanner - Passive/release) 15 | 10021 WARN (X-Content-Type-Options Header Missing - Passive/release) 16 | 10023 WARN (Information Disclosure - Debug Error Messages - Passive/beta) 17 | 10024 WARN (Information Disclosure - Sensitive Informations in URL - Passive/beta) 18 | 10025 WARN (Information Disclosure - Sensitive Information in HTTP Referrer Header - Passive/beta) 19 | 10026 WARN (HTTP Parameter Override - Passive/beta) 20 | 10027 WARN (Information Disclosure - Suspicious Comments - Passive/beta) 21 | 10032 WARN (Viewstate Scanner - Passive/beta) 22 | 10040 WARN (Secure Pages Include Mixed Content - Passive/release) 23 | 10045 WARN (Source Code Disclosure - /WEB-INF folder - Active/beta) 24 | 10048 WARN (Remote Code Execution - Shell Shock - Active/beta) 25 | 10095 WARN (Backup File Disclosure - Active/beta) 26 | 10105 WARN (Weak Authentication Method - Passive/beta) 27 | 10202 WARN (Absence of Anti-CSRF Tokens - Passive/beta) 28 | 2 WARN (Private IP Disclosure - Passive/release) 29 | 20012 WARN (Anti CSRF Tokens Scanner - Active/beta) 30 | 20014 WARN (HTTP Parameter Pollution scanner - Active/beta) 31 | 20015 WARN (Heartbleed OpenSSL Vulnerability - Active/beta) 32 | 20016 WARN (Cross-Domain Misconfiguration - Active/beta) 33 | 20017 WARN (Source Code Disclosure - CVE-2012-1823 - Active/beta) 34 | 20018 WARN (Remote Code Execution - CVE-2012-1823 - Active/beta) 35 | 20019 WARN (External Redirect - Active/release) 36 | 3 WARN (Session ID in URL Rewrite - Passive/release) 37 | 30001 WARN (Buffer Overflow - Active/release) 38 | 30002 WARN (Format String Error - Active/release) 39 | 30003 WARN (Integer Overflow Error - Active/beta) 40 | 40003 WARN (CRLF Injection - Active/release) 41 | 40008 WARN (Parameter Tampering - Active/release) 42 | 40009 WARN (Server Side Include - Active/release) 43 | 40012 WARN (Cross Site Scripting (Reflected) - Active/release) 44 | 40013 WARN (Session Fixation - Active/beta) 45 | 40014 WARN (Cross Site Scripting (Persistent) - Active/release) 46 | 40016 WARN (Cross Site Scripting (Persistent) - Prime - Active/release) 47 | 40017 WARN (Cross Site Scripting (Persistent) - Spider - Active/release) 48 | 40018 WARN (SQL Injection - Active/release) 49 | 40019 WARN (SQL Injection - MySQL - Active/beta) 50 | 40020 WARN (SQL Injection - Hypersonic SQL - Active/beta) 51 | 40021 WARN (SQL Injection - Oracle - Active/beta) 52 | 40022 WARN (SQL Injection - PostgreSQL - Active/beta) 53 | 40023 WARN (Possible Username Enumeration - Active/beta) 54 | 42 WARN (Source Code Disclosure - SVN - Active/beta) 55 | 50000 WARN (Script Active Scan Rules - Active/release) 56 | 50001 WARN (Script Passive Scan Rules - Passive/release) 57 | 6 WARN (Path Traversal - Active/release) 58 | 7 WARN (Remote File Inclusion - Active/release) 59 | 90001 WARN (Insecure JSF ViewState - Passive/beta) 60 | 90011 WARN (Charset Mismatch - Passive/beta) 61 | 90019 WARN (Server Side Code Injection - Active/release) 62 | 90020 WARN (Remote OS Command Injection - Active/release) 63 | 90021 WARN (XPath Injection - Active/beta) 64 | 90022 WARN (Application Error Disclosure - Passive/release) 65 | 90023 WARN (XML External Entity Attack - Active/beta) 66 | 90024 WARN (Generic Padding Oracle - Active/beta) 67 | 90025 WARN (Expression Language Injection - Active/beta) 68 | 90026 WARN (SOAP Action Spoofing - Active/alpha) 69 | 90028 WARN (Insecure HTTP Method - Active/beta) 70 | 90029 WARN (SOAP XML Injection - Active/alpha) 71 | 90030 WARN (WSDL File Passive Scanner - Passive/alpha) 72 | 90033 WARN (Loosely Scoped Cookie - Passive/beta) 73 | -------------------------------------------------------------------------------- /zap/zap-baseline.conf: -------------------------------------------------------------------------------- 1 | # zap-baseline rule configuration file 2 | # change FAIL to IGNORE to ignore rule or FAIL to fail if rule matches 3 | # only the rule identifiers are used - the names are just for info 4 | 10010 FAIL (Cookie No HttpOnly Flag) 5 | 10011 FAIL (Cookie Without Secure Flag) 6 | 10012 IGNORE (Password Autocomplete in browser) 7 | 10016 FAIL (Web Browser XSS Protection Not Enabled) 8 | 10017 FAIL (Cross-Domain JavaScript Source File Inclusion) 9 | 10019 FAIL (Content-Type Header Missing) 10 | 10020 FAIL (X-Frame-Options Header Not Set) 11 | 10021 FAIL (X-Content-Type-Options Header Missing) 12 | 10034 FAIL (Heartbleed OpenSSL Vulnerability (Indicative)) 13 | 10035 FAIL (Strict-Transport-Security Header Not Set) 14 | 10038 FAIL (Content Security Policy (CSP) Header Not Set) 15 | 10040 FAIL (Secure Pages Include Mixed Content) 16 | 10052 FAIL (X-ChromeLogger-Data (XCOLD) Header Information Leak) 17 | 10098 FAIL (Cross-Domain Misconfiguration) 18 | 40014 FAIL (Absence of Anti-CSRF Tokens) 19 | --------------------------------------------------------------------------------