PHP Race Condition Vulnerability Example

This article based-on the excellent article "Practical Race Condition Vulnerabilities in Web Applications". I have made my own sample source-code for testing.

================
# Let's start!

Log-into MySQL(or MariaDB) Shell to create sample database

MariaDB [test]> create database test character set utf8 collate utf8_general_ci;
MariaDB [test]> grant all privileges on test.* to test@'localhost' identified by 'test@123';
MariaDB [test]> flush privileges;
MariaDB [test]> create table bank_accounts(uid int auto_increment primary key,ucode varchar(10) not null,balance int(11) not null default 0,uname varchar(50) not null);
MariaDB [test]> insert into bank_accounts(ucode,uname,balance) values ('BANK000001','User 1',20000),('BANK000002','User 2',5500),('BANK000003','User 3',8700);

We'll test with User-1's account.

MariaDB [test]> select * from bank_accounts;
+-----+------------+---------+--------+
| uid | ucode      | balance | uname  |
+-----+------------+---------+--------+
|   1 | BANK000001 |   20000 | User 1 |
|   2 | BANK000002 |    5500 | User 2 |
|   3 | BANK000003 |    8700 | User 3 |
+-----+------------+---------+--------+
3 rows in set (0.00 sec)

Source code for testing

$ git clone https://github.com/doantranhoang/php-race-condition-example.git
$ cd php-race-condition-example
  • poc.php: move into your web-server directory.
  • withdraw_normal.py: Code for the first test-case.
  • withdraw_race.py: Code for the second test-case with race condition vulnerability.
Change the url_test variable in withdraw_normal.py and withdraw_normal.py by your own link.

==================
# First test-case: Withdrawing money 128 times sequentially, $100 each time.
Run something

$ python withdraw_normal.py 

Your balance before withdraw $100 is $20000
Your balance after withdraw $100 is $19900

Your balance before withdraw $100 is $19900
Your balance after withdraw $100 is $19800

...
...
...
...

Your balance before withdraw $100 is $7500
Your balance after withdraw $100 is $7400

Your balance before withdraw $100 is $7400
Your balance after withdraw $100 is $7300

Your balance before withdraw $100 is $7300
Your balance after withdraw $100 is $7200

Check User-1's balance in DB. The balance will (probably) equal 20000 - 128*100 = $7200 by logical calculate.

MariaDB [test]> select * from bank_accounts;
+-----+------------+---------+--------+
| uid | ucode      | balance | uname  |
+-----+------------+---------+--------+
|   1 | BANK000001 |    7200 | User 1 |
|   2 | BANK000002 |    5500 | User 2 |
|   3 | BANK000003 |    8700 | User 3 |
+-----+------------+---------+--------+
3 rows in set (0.00 sec)

It's OK, nothing wrong here.
Return User-1's balance to $20000 for the next test-case

MariaDB [test]> update bank_accounts set balance = 20000 where uid = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MariaDB [test]> select * from bank_accounts;
+-----+------------+---------+--------+
| uid | ucode      | balance | uname  |
+-----+------------+---------+--------+
|   1 | BANK000001 |   20000 | User 1 |
|   2 | BANK000002 |    5500 | User 2 |
|   3 | BANK000003 |    8700 | User 3 |
+-----+------------+---------+--------+
3 rows in set (0.00 sec)

==================
# Next test-case: Withdrawing money 128 times with 128 concurrent process, $100 each process.

$ python withdraw_race.py 

Your balance after withdraw $100 is $10500

Your balance before withdraw $100 is $10700
Your balance after withdraw $100 is $10500

Your balance before withdraw $100 is $10700
...
...
...

Your balance after withdraw $100 is $8200

Your balance before withdraw $100 is $8300
Your balance after withdraw $100 is $8100

Your balance before withdraw $100 is $8200
Your balance after withdraw $100 is $8100

Check User-1's balance in DB. The balance will (probably) equal 20000 - 128*100 = $7200 by logical calculate. But wait, something not logical here... User-1's balance now is $8100

MariaDB [test]> select * from bank_accounts;
+-----+------------+---------+--------+
| uid | ucode      | balance | uname  |
+-----+------------+---------+--------+
|   1 | BANK000001 |    8100 | User 1 |
|   2 | BANK000002 |    5500 | User 2 |
|   3 | BANK000003 |    8700 | User 3 |
+-----+------------+---------+--------+
3 rows in set (0.00 sec)

Race condition vulnerability here => Hackers (User-1 in our example) stole 8100 - 7200 = $900 from his bank.

Enjoy hacking!

Nhận xét

Bài đăng phổ biến từ blog này

CVE-2019-12839: Lỗ hổng thực thi mã lệnh tùy ý trên OrangeHRM CMS

[Steganography] Kỹ thuật che dấu thông tin - Phần 2