Skip to content

Commit df6c0fd

Browse files
committed
Add test to prove a path traversal attack
If I try to retrieve the translations using the following url: http://localhost/translations?locales=randomstring/something The file `something.js` gets created in the subdirectory `messages.randomstring` of the cache directory: /var/www/someproject/app/cache/dev/bazinga-js-translation/messages.randomstring/something.js This is the actual string that gets passed to the constructor of `ConfigCache` by the JsTranslationBundle controller. I can now traverse down from the JsTranslationBundle cache directory (without first creating the `messages.randomstring` directory using the previous step, this won't work): http://localhost/translations?locales=randomstring/../../evil becomes: /var/www/someproject/app/cache/dev/bazinga-js-translation/messages.randomstring/../../evil.js ... and depending on the configuration of the server, I could also do: http://localhost/translations?locales=randomstring/../../../../../web/evil => /var/www/someproject/app/cache/dev/bazinga-js-translation/messages.randomstring/../../../../../web/evil.js Thus creating the file `evil.js` (and `evil.js.meta`) under the Symfony `web` root. Depending on file system permissions, this will also overwrite existing files. Signed-off-by: William DURAND <william.durand1@gmail.com>
1 parent 360aa09 commit df6c0fd

File tree

2 files changed

+33
-1
lines changed

2 files changed

+33
-1
lines changed

Controller/Controller.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use Symfony\Component\HttpFoundation\Request;
1010
use Symfony\Component\Config\ConfigCache;
1111
use Symfony\Component\Config\Resource\FileResource;
12+
use Symfony\Component\Filesystem\Exception\IOException;
13+
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
1214

1315
/**
1416
* @author William DURAND <william.durand1@gmail.com>
@@ -143,7 +145,11 @@ public function getTranslationsAction(Request $request, $domain, $_format)
143145
'include_config' => true,
144146
));
145147

146-
$cache->write($content, $resources);
148+
try {
149+
$cache->write($content, $resources);
150+
} catch (IOException $e) {
151+
throw new NotFoundHttpException();
152+
}
147153
}
148154

149155
$response = new Response(

Tests/Controller/ControllerTest.php

+26
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,30 @@ public function testGetTranslationsWithNumericKeys()
171171
JSON
172172
, $response->getContent());
173173
}
174+
175+
public function testGetTranslationsWithPathTraversalAttack()
176+
{
177+
$client = static::createClient();
178+
179+
// 1. `evil.js` is not accessible
180+
$crawler = $client->request('GET', '/translations?locales=randomstring/../../evil');
181+
$response = $client->getResponse();
182+
183+
$this->assertEquals(404, $response->getStatusCode());
184+
185+
// 2. let's create a random directory with a randome js file
186+
$crawler = $client->request('GET', '/translations?locales=randomstring/something');
187+
$response = $client->getResponse();
188+
189+
$this->assertFileExists(sprintf('%s/%s/messages.randomstring/something.js',
190+
$client->getKernel()->getCacheDir(),
191+
'bazinga-js-translation'
192+
));
193+
194+
// 3. path traversal attack
195+
$crawler = $client->request('GET', '/translations?locales=randomstring/../../evil');
196+
$response = $client->getResponse();
197+
198+
$this->assertEquals(200, $response->getStatusCode());
199+
}
174200
}

0 commit comments

Comments
 (0)