Ungit Uses a Hardcoded Express Session Secret When Authentication Is Enabled
Ungit Uses a Hardcoded Express Session Secret When Authentication Is Enabled
Package
Ungit
Affected versions
Versions containing the affected code when authentication is enabled.
The affected code is present at commit b1df1a168b9208766342070ebd42782036dfd18d.
Patched versions
Unknown.
Description
Summary
Ungit configures express-session with the hardcoded secret ungit:
session({
store: new MemoryStore({
checkPeriod: 86400000,
}),
secret: 'ungit',
resave: true,
saveUninitialized: true,
})Source: source/server.js.
When authentication is enabled, Ungit stores the authenticated username in a
server-side Express session. Protected Git APIs call ensureAuthenticated and
check req.isAuthenticated().
The default session store is MemoryStore. Therefore, the public secret does
not by itself create an authenticated session for an arbitrary username. An
attacker would also need a valid target Session ID, or a separate session
fixation/session disclosure primitive. The current evidence supports a
hardcoded session-integrity secret, but does not prove a direct authentication
bypass from the secret alone.
Vulnerable configuration
Authentication is disabled by default and can be enabled through the Ungit configuration:
authentication: false,
users: {},Source: source/config.js.
Authentication flow
The configured username/password map is used by Passport LocalStrategy:
passport.use(
new LocalStrategy((username, password, done) => {
if (users[username] !== undefined && password === users[username]) {
done(null, username);
} else {
done(null, false, { message: 'No such username/password' });
}
})
);Source: source/server.js.
Passport serializes only the username into the server-side session:
passport.serializeUser((username, done) => {
done(null, username);
});
passport.deserializeUser((username, done) => {
done(null, users[username] !== undefined ? username : null);
});Source: source/server.js.
The protected API checks the server-side authentication state:
ensureAuthenticated = (req, res, next) => {
if (req.isAuthenticated()) {
return next();
}
res.status(401).json({
errorCode: 'authentication-required',
});
};Source: source/server.js.
For example, Git operations are protected by this middleware:
app.get(`${exports.pathPrefix}/status`, ensureAuthenticated, ensurePathExists, handler);
app.post(`${exports.pathPrefix}/clone`, ensureAuthenticated, ensurePathExists, handler);
app.post(`${exports.pathPrefix}/push`, ensureAuthenticated, ensurePathExists, handler);Source: source/git-api.js and source/git-api.js.
Proof of concept
Run this only against a local Ungit instance with authentication: true and a
test user.
First, create a correctly signed cookie for a random Session ID using the
public value ungit:
const signature = require('cookie-signature');
const sessionId = 'attacker-chosen-session-id';
const signedCookie = `s:${sessionId}.${signature.sign(sessionId, 'ungit')}`;
console.log(`connect.sid=${encodeURIComponent(signedCookie)}`);Send it to the local instance:
curl -i \
-H 'Cookie: connect.sid=s%3Aattacker-chosen-session-id.<signature>' \
http://127.0.0.1:8448/api/loggedinExpected result: {"loggedIn":false}. The server does not find the chosen
Session ID in MemoryStore, creates a new unauthenticated session, and does
not treat the attacker-chosen ID as a username.
As a control, log in with the configured test username and password, retain
the returned connect.sid, and request /api/loggedin again. The result is
{"loggedIn":true}. This demonstrates that the authentication state is stored
server-side rather than in the signed cookie.
Impact
The hardcoded value weakens the integrity protection of Ungit's session
cookies. If a valid Session ID is disclosed through logs, monitoring, a proxy,
or another vulnerability, knowledge of ungit allows an attacker to recreate
the cookie signature for that ID.
The hardcoded value alone does not prove arbitrary-user impersonation because
the session data is held in MemoryStore and contains the authenticated
username server-side. A direct authentication-bypass report would require an
additional finding such as predictable or attacker-controlled Session IDs,
session fixation, or Session ID disclosure.
Recommended fix
- Replace
secret: 'ungit'with a required, deployment-specific, high-entropy secret. - Fail closed when authentication is enabled and no session secret is configured.
- Use a production session store instead of the in-process
MemoryStorefor multi-process or exposed deployments. - Regenerate the Session ID after successful login and invalidate sessions after password changes.
- Restrict exposed Ungit instances with network controls such as localhost binding or an authenticated reverse proxy.
References
Source: FredrikNoren/ungit