Russian version can be found here
Let’s open the website. We can see only login form here.
dirsearch on this host and found names like
sessions. It probably means that it’s a node.js application. Also we can find file
js/main.js, which makes requests to API. We noticed that
autoindex was enabled and we’re able to see the whole
js/ directory. And there is server side sources.
There is saved copy of
Application is written on Express framework and served on port 4000. There is nginx on port 80, which forwards requests to Express. Application has only three methods:
/api/logout are not implemented. They return redirects to
/login. Last endpoint checks your session and shows flag only if your name is
It turns out that all unknown requests are forwarded to port 5000 from
config.js file. But this port isn’t available from outside. Also we found secret key which is used in session signing. It seemed that task will be easy, but sessions are stored in files, not in cookies. We can’t create new session, and there is no sessions in the respective directory. So we’ll just remember it, right?
There is frontend script
js/main.js. And there is request to
/api/images. We can guess that this method works with files and it may help us. But we get error 403 when requesting the page.
Let’s get back to server sources. There is session check in
auth.js file, so we can access only
/api/logout. Also we noted that there is no
/api/images implementation in Express application, so it may be implemented in the hidden application on port 5000.
We’re not able to pass this check at the moment, but let’s try to find out something about that hidden app. We discovered by accident that if we send
OPTIONS request to
/api/logout, we’ll get Laravel error page. It’s PHP framework. But why? It’s obvious: we aren’t required to log in because path is whitelisted, but there is no Express endpoint for this path and method.
Then we can discover or remember that Express doesn’t normalize paths. Therefore authorization bypass is easy: we just need to follow URL
//api/images. We got
200 OK there and empty list. If we substitute
year parameter, we’ll get image list. So we have directories listing and it’s vulnerable to Directory traversal.
The debug mode was switched on and it helped us to understand that there is
/img/ after year. So we can’t see arbitrary directory contents. But you may remember about classic PHP bug — null byte injection: C functions recognize
%00 as the end of string. It helps us to avoid
/img/ ending and to list any directory in the filesystem.
Flag is located in
/var/www/flag, but we can’t access it using
/api/image because of
file_exists call: it fails when there is
%00 in string. Also there is filtering in
img parameter, so nothing helps.
Then we noted three applications located in
/var/www/apps. We know two of them:
volga_gallery is Laravel app and
volga_auth is Express app. But we don’t know anything about
sessions here, but this directory contains one session. It’s very suspicious. We thought about it and guessed that this session may contain
name=admin property. Then we started to search this “admin panel”, but we failed. In fact it’s a bit harder.
As you know, letter S in
npm stands for security. So we remembered that sessions are stored inside JSON files. This functionality is provided by session-file-store module. We’ve got into this library and found how session filename is generated. It’s just prefix (
sessions/ in our case) concatenated with session ID from cookie with a slash.
But we don’t have sessions and they’re signed with secret key, so we can’t put anything malicious here. However we have one: what will happen if we substitute
../../volga_adminpanel/sessions/euzb7bMKx-5F29b2xNobGTDoWXmVFlEM as session ID? As you may guess, Express will find the session. And we have secret key, so we can sign a session.
We didn’t want to explore how session signing works in Express, so we ran copy of application locally, added session signing right inside
express-session module and triggered it with an additional endpoint:
We got a cookie so let’s get