add an auth token to any URL that modifies the system, to avoid CSRF vulnerabilities

This commit is contained in:
Mike Bonnet 2009-01-19 12:32:49 -05:00
parent 84b0b40615
commit 99143b3413
13 changed files with 76 additions and 16 deletions

View file

@ -9,6 +9,7 @@
#end if
<form action="#if $target then 'buildtargetedit' else 'buildtargetcreate'#">
$util.authToken($self, form=True)
#if $target
<input type="hidden" name="targetID" value="$target.id"/>
#end if

View file

@ -19,10 +19,10 @@
</tr>
#if 'admin' in $perms
<tr>
<th colspan="2"><a href="buildtargetedit?targetID=$target.id">Edit</a></th>
<th colspan="2"><a href="buildtargetedit?targetID=$target.id$util.authToken($self)">Edit</a></th>
</tr>
<tr>
<th colspan="2"><a href="buildtargetdelete?targetID=$target.id">Delete</a></th>
<th colspan="2"><a href="buildtargetdelete?targetID=$target.id$util.authToken($self)">Delete</a></th>
</tr>
#end if
</table>

View file

@ -70,7 +70,7 @@
#if 'admin' in $perms
<br/>
<a href="buildtargetcreate">Create new Build Target</a>
<a href="buildtargetcreate$util.authToken($self, first=True)">Create new Build Target</a>
#end if
#include "includes/footer.chtml"

View file

@ -27,9 +27,9 @@
$util.imageTag($enabled)
#if 'admin' in $perms
#if $host.enabled
<span class="adminLink">(<a href="disablehost?hostID=$host.id">disable</a>)</span>
<span class="adminLink">(<a href="disablehost?hostID=$host.id$util.authToken($self)">disable</a>)</span>
#else
<span class="adminLink">(<a href="enablehost?hostID=$host.id">enable</a>)</span>
<span class="adminLink">(<a href="enablehost?hostID=$host.id$util.authToken($self)">enable</a>)</span>
#end if
#end if
</td>

View file

@ -168,8 +168,8 @@
<td>#if $notif.package then $notif.package.name else 'all'#</td>
<td>#if $notif.tag then $notif.tag.name else 'all'#</td>
<td>#if $notif.success_only then 'success only' else 'all'#</td>
<td><a href="notificationedit?notificationID=$notif.id">edit</a></td>
<td><a href="notificationdelete?notificationID=$notif.id">delete</a></td>
<td><a href="notificationedit?notificationID=$notif.id$util.authToken($self)">edit</a></td>
<td><a href="notificationdelete?notificationID=$notif.id$util.authToken($self)">delete</a></td>
</tr>
#end for
#if $len($notifs) == 0
@ -180,7 +180,7 @@
</table>
<br/>
<a href="notificationcreate">Add a Notification</a>
<a href="notificationcreate$util.authToken($self, first=True)">Add a Notification</a>
#end if
#include "includes/footer.chtml"

View file

@ -8,6 +8,7 @@ import Cheetah.Filters
import Cheetah.Template
import datetime
import time
import md5
import koji
import kojiweb.util
@ -49,6 +50,30 @@ def _getUserCookie(req):
return None
def _truncTime():
now = datetime.datetime.now()
# truncate to the nearest 15 minutes
return now.replace(minute=(now.minute / 15 * 15), second=0, microsecond=0)
def _genToken(req, tstamp=None):
if hasattr(req, 'currentLogin') and req.currentLogin:
user = req.currentLogin
else:
return ''
if tstamp == None:
tstamp = _truncTime()
return md5.new(user + str(tstamp) + req.get_options()['Secret']).hexdigest()[-8:]
def _getValidTokens(req):
tokens = []
now = _truncTime()
for delta in (0, 15, 30):
token_time = now - datetime.timedelta(minutes=delta)
token = _genToken(req, token_time)
if token:
tokens.append(token)
return tokens
def _krbLogin(req, session, principal):
options = req.get_options()
wprinc = options['WebPrincipal']
@ -81,6 +106,19 @@ def _assertLogin(req):
raise koji.AuthError, 'could not login using principal: %s' % req.currentLogin
else:
raise koji.AuthError, 'KojiWeb is incorrectly configured for authentication, contact the system administrator'
# verify a valid authToken was passed in to avoid CSRF
authToken = req.form.get('a', '')
validTokens = _getValidTokens(req)
if authToken and authToken in validTokens:
# we have a token and it's valid
pass
else:
# their authToken is likely expired
# send them back to the page that brought them here so they
# can re-click the link with a valid authToken
_redirectBack(req, page=None, forceSSL=(_getBaseURL(req).startswith('https://')))
assert False
else:
mod_python.util.redirect(req, 'login')
assert False
@ -117,7 +155,8 @@ def _genHTML(req, fileName):
req._values['currentUser'] = req.currentUser
else:
req._values['currentUser'] = None
req._values['authToken'] = _genToken(req)
tmpl_class = TEMPLATES.get(fileName)
if not tmpl_class:
tmpl_class = Cheetah.Template.Template.compile(file=fileName)

View file

@ -9,6 +9,7 @@
#end if
<form action="#if $notif then 'notificationedit' else 'notificationcreate'#">
$util.authToken($self, form=True)
#if $notif
<input type="hidden" name="notificationID" value="$notif.id"/>
#end if

View file

@ -9,6 +9,7 @@
#end if
<form action="#if $tag then 'tagedit' else 'tagcreate'#">
$util.authToken($self, form=True)
<table>
<tr>
<th>Name</th>

View file

@ -7,7 +7,7 @@
<table>
#if $child and 'admin' in $perms
<tr>
<th colspan="2"><a href="tagparent?tagID=$child.id&parentID=$tag.id&action=add">Add $tag.name as parent of $child.name</a></th>
<th colspan="2"><a href="tagparent?tagID=$child.id&parentID=$tag.id&action=add$util.authToken($self)">Add $tag.name as parent of $child.name</a></th>
</tr>
#end if
<tr>
@ -54,7 +54,7 @@
<span class="treeLabel">
<a href="taginfo?tagID=$parent.parent_id">$parent.name</a>
#if $depth == 1 and 'admin' in $perms
<span class="treeLink">(<a href="tagparent?tagID=$tag.id&parentID=$parent.parent_id&action=edit">edit</a>) (<a href="tagparent?tagID=$tag.id&parentID=$parent.parent_id&action=remove">remove</a>)</span>
<span class="treeLink">(<a href="tagparent?tagID=$tag.id&parentID=$parent.parent_id&action=edit$util.authToken($self)">edit</a>) (<a href="tagparent?tagID=$tag.id&parentID=$parent.parent_id&action=remove$util.authToken($self)">remove</a>)</span>
#end if
</span>
</span>
@ -125,10 +125,10 @@
</tr>
#if 'admin' in $perms
<tr>
<td colspan="2"><a href="tagedit?tagID=$tag.id">Edit tag</a></td>
<td colspan="2"><a href="tagedit?tagID=$tag.id$util.authToken($self)">Edit tag</a></td>
</tr>
<tr>
<td colspan="2"><a href="tagdelete?tagID=$tag.id">Delete tag</a></td>
<td colspan="2"><a href="tagdelete?tagID=$tag.id$util.authToken($self)">Delete tag</a></td>
</tr>
#end if
</table>

View file

@ -9,6 +9,7 @@
#end if
<form action="tagparent">
$util.authToken($self, form=True)
<input type="hidden" name="action" value="#if $inheritanceData then 'edit' else 'add'#"/>
<table>
<tr>

View file

@ -70,7 +70,7 @@
#if 'admin' in $perms
<br/>
<a href="tagcreate">Create new Tag</a>
<a href="tagcreate$util.authToken($self, first=True)">Create new Tag</a>
#end if
#include "includes/footer.chtml"

View file

@ -171,9 +171,9 @@
<td class="task$state">$state
#if $currentUser and ('admin' in $perms or $task.owner == $currentUser.id)
#if $task.state in ($koji.TASK_STATES.FREE, $koji.TASK_STATES.OPEN, $koji.TASK_STATES.ASSIGNED)
<span class="adminLink">(<a href="canceltask?taskID=$task.id">cancel</a>)</span>
<span class="adminLink">(<a href="canceltask?taskID=$task.id$util.authToken($self)">cancel</a>)</span>
#elif $task.state in ($koji.TASK_STATES.CANCELED, $koji.TASK_STATES.FAILED) and (not $parent)
<span class="adminLink">(<a href="resubmittask?taskID=$task.id">resubmit</a>)</span>
<span class="adminLink">(<a href="resubmittask?taskID=$task.id$util.authToken($self)">resubmit</a>)</span>
#end if
#end if
</td>

View file

@ -316,6 +316,23 @@ def escapeHTML(value):
replace('<', '&lt;').\
replace('>', '&gt;')
def authToken(template, first=False, form=False):
"""Return the current authToken if it exists.
If form is True, return it enclosed in a hidden input field.
Otherwise, return it in a format suitable for appending to a URL.
If first is True, prefix it with ?, otherwise prefix it
with &. If no authToken exists, return an empty string."""
token = template.getVar('authToken', default=None)
if token != None:
if form:
return '<input type="hidden" name="a" value="%s"/>' % token
if first:
return '?a=' + token
else:
return '&a=' + token
else:
return ''
def explainError(error):
"""Explain an exception in user-consumable terms