Hi, I plan to release a DSA fixing this issue with the attached patch. Please upload a new version to sid containing the fix.
Cheers, -- Raphael Geissert - Debian Developer www.debian.org - get.debian.net
Fix CVE-2009-1629: weak session id generation Use a cookie with a strong random name and a strong random value for each user to protect from session id hijacking attacks. The browser-generated session id is now stronger but only used to separate multiple sessions of the same ip. Additionally, a limiting mechanism sets a maximum of 20 simultaneous sessions in total and 4 maximum sessions per connecting ip. The X-Forwarded-For header is only honoured if the real remote ip is 127.0.0.1. diff -urpN ajaxterm-0.10-4.orig/ajaxterm.js ajaxterm-0.10-4/ajaxterm.js --- ajaxterm-0.10-4.orig/ajaxterm.js 2006-10-28 21:52:39.000000000 -0500 +++ ajaxterm-0.10-4/ajaxterm.js 2010-02-04 19:45:11.000000000 -0600 @@ -3,7 +3,24 @@ ajaxterm.Terminal_ctor=function(id,width var ie=0; if(window.ActiveXObject) ie=1; - var sid=""+Math.round(Math.random()*1000000000); + + var sid=""; + + for (var i=0; i < 128; i++) { + var r = 0; + // now get a random number between 0 and 255 + // numbers not in the range are intentionally discarded + // as it reduces the chance of predicting the seed, by not + // using all of the numbers generated by the PRNG + do { + r = Math.round(Math.random()*1000); + } while(r >= 255); + r = r.toString(16); + if (r.length == 1) + r = "0"+r; + sid += "%" + r; + } + var query0="s="+sid+"&w="+width+"&h="+height; var query1=query0+"&c=1&k="; var buf=""; diff -urpN ajaxterm-0.10-4.orig/ajaxterm.py ajaxterm-0.10-4/ajaxterm.py --- ajaxterm-0.10-4.orig/ajaxterm.py 2006-10-28 21:52:39.000000000 -0500 +++ ajaxterm-0.10-4/ajaxterm.py 2010-02-04 20:01:50.000000000 -0600 @@ -2,7 +2,14 @@ """ Ajaxterm """ -import array,cgi,fcntl,glob,mimetypes,optparse,os,pty,random,re,signal,select,sys,threading,time,termios,struct,pwd +import array,cgi,fcntl,glob,mimetypes,optparse,os,pty,random,re,signal,select,sys,threading,time,termios,struct,pwd,Cookie +from datetime import datetime, timedelta + +try: + from hashlib import sha1 +except ImportError: + import sha + sha1 = sha.new os.chdir(os.path.normpath(os.path.dirname(__file__))) # Optional: Add QWeb in sys path @@ -483,30 +490,61 @@ class AjaxTerm: self.mime['.html']= 'text/html; charset=UTF-8' self.multi = Multiplex(cmd) self.session = {} + self.session_ip = {} + self.sessions_limit = 20 + self.sessions_user_limit = 4 + m = sha1() + m.update(os.urandom(128)) + self.cookie_name = m.hexdigest() def __call__(self, environ, start_response): req = qweb.QWebRequest(environ, start_response,session=None) if req.PATH_INFO.endswith('/u'): + req.response_headers['Content-Type']='text/xml' + uid="" + if self.cookie_name not in req.request_cookies: + req.write('<?xml version="1.0"?><idem></idem>') + return req + uid = req.request_cookies[self.cookie_name].value s=req.REQUEST["s"] k=req.REQUEST["k"] c=req.REQUEST["c"] w=req.REQUEST.int("w") h=req.REQUEST.int("h") - if s in self.session: - term=self.session[s] + ip="unknown" + if environ.has_key("REMOTE_ADDR"): + ip=environ['REMOTE_ADDR'] + if ip == "127.0.0.1" and environ.has_key("HTTP_X_FORWARDED_FOR"): + ip=environ["HTTP_X_FORWARDED_FOR"] + + if (uid+s) in self.session: + term=self.session[uid+s] + req.response_cookies.load(req.request_cookies[self.cookie_name].OutputString()) + req.response_cookies[self.cookie_name]['expires'] = datetime.utcnow()+timedelta(seconds=60) else: if not (w>2 and w<256 and h>2 and h<100): w,h=80,25 - term=self.session[s]=self.multi.create(w,h) + # check if there aren't too many open sessions + if len(self.session) < self.sessions_limit: + count=0 + for i in self.session_ip.keys(): + if self.session_ip[i] == ip: + count+=1 + if count <= self.sessions_user_limit: + term=self.session[uid+s]=self.multi.create(w,h) + self.session_ip[uid+s]=ip + else: + req.write('<?xml version="1.0"?><idem></idem>') + return req if k: self.multi.proc_write(term,k) time.sleep(0.002) dump=self.multi.dump(term,c) - req.response_headers['Content-Type']='text/xml' if isinstance(dump,str): req.write(dump) req.response_gzencode=1 else: - del self.session[s] + del self.session[uid+s] + del self.session_ip[uid+s] req.write('<?xml version="1.0"?><idem></idem>') # print "sessions %r"%self.session else: @@ -515,9 +553,23 @@ class AjaxTerm: req.response_headers['Content-Type'] = self.mime.get(os.path.splitext(n)[1].lower(), 'application/octet-stream') req.write(self.files[n]) else: + if self.cookie_name not in req.request_cookies: + self.genSidCookie(req) req.response_headers['Content-Type'] = 'text/html; charset=UTF-8' req.write(self.files['index']) return req + def genSidCookie(self, req): + m = sha1() + m.update(os.urandom(160)) + req.response_cookies[self.cookie_name] = m.hexdigest() + # try to set httponly if supported (added in 2.6) + try: + req.response_cookies[self.cookie_name]['httponly'] = 1 + except (Cookie.CookieError): + pass + req.response_cookies[self.cookie_name]['path'] = req.PATH_INFO + req.response_cookies[self.cookie_name]['expires'] = datetime.utcnow()+timedelta(seconds=60) + return req def main(): parser = optparse.OptionParser()
signature.asc
Description: This is a digitally signed message part.