├── Readme.md ├── gae ├── app.yaml ├── index.yaml ├── level0.py ├── level1.py ├── level2.py ├── level3.py ├── level4.py ├── main.py ├── mako │ ├── __init__.py │ ├── _ast_util.py │ ├── ast.py │ ├── cache.py │ ├── codegen.py │ ├── compat.py │ ├── exceptions.py │ ├── ext │ │ ├── __init__.py │ │ ├── autohandler.py │ │ ├── babelplugin.py │ │ ├── beaker_cache.py │ │ ├── preprocessors.py │ │ ├── pygmentplugin.py │ │ └── turbogears.py │ ├── filters.py │ ├── lexer.py │ ├── lookup.py │ ├── parsetree.py │ ├── pygen.py │ ├── pyparser.py │ ├── runtime.py │ ├── template.py │ └── util.py ├── static │ ├── exploit.html │ ├── favicon.png │ ├── img │ │ ├── down.png │ │ └── up.png │ ├── report0.txt │ ├── report01.html │ ├── report01.md │ ├── report23.html │ ├── report23.md │ ├── report47.html │ └── report47.md └── templates │ ├── home.html │ ├── level0 │ └── home.html │ ├── level1 │ ├── home.html │ ├── post.html │ └── posted.html │ ├── level2 │ ├── edit.html │ └── home.html │ ├── level3 │ ├── admin.html │ └── home.html │ └── level4 │ ├── comments.html │ ├── delete.html │ ├── home.html │ └── submit.html └── levels58 ├── Procfile ├── db.py ├── el12_sandbox └── keep_me ├── handler.py ├── handlers ├── __init__.py ├── el10.py ├── el11.py ├── el12.py ├── el13.py ├── exam1.py ├── level5.py ├── level6.py ├── level7.py └── level8.py ├── level5_docs ├── 42.txt └── ebooks │ └── alice.txt ├── level8_sandbox └── keep_me ├── main.py ├── requirements.txt ├── static └── favicon.png └── templates ├── exam1 ├── authed.html ├── el10 │ ├── admin_login.html │ └── index.html ├── el11 │ └── index.html ├── el12 │ ├── create.html │ ├── edit.html │ └── index.html ├── el13 │ ├── feedback.html │ ├── index.html │ └── login.html └── index.html ├── level5 └── index.html ├── level6 ├── add.html ├── edit.html └── index.html ├── level7 ├── index.html └── success.html └── level8 └── index.html /Readme.md: -------------------------------------------------------------------------------- 1 | Note 2 | ==== 3 | 4 | The code in this repo is from the old Hacker101 coursework, deprecated in favor of the [Hacker101 CTF](https://ctf.hacker101.com/). It is split into two parts: one that runs and depends on Google App Engine and one that can be run anywhere you have Python and a MySQL database. 5 | 6 | Google App Engine 7 | ================= 8 | 9 | The GAE levels live under the `gae` directory and should be deployable like any Python application on Google App Engine. 10 | 11 | Plain Python 12 | ============ 13 | 14 | Simply run `pip install -r requirements.txt` then change db.py to point to your MySQL instance. At that point, you should be able to run `python main.py` and have a running instance. 15 | 16 | There are several secret levels (that is, were never used in Hacker101); figuring out access is left as an exercise to the hacker. 17 | -------------------------------------------------------------------------------- /gae/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: python27 2 | api_version: 1 3 | threadsafe: yes 4 | 5 | builtins: 6 | - remote_api: on 7 | 8 | handlers: 9 | - url: /favicon\.png 10 | static_files: static/favicon.png 11 | upload: static/favicon\.png 12 | 13 | - url: /css 14 | static_dir: static/css 15 | 16 | - url: /js 17 | static_dir: static/js 18 | 19 | - url: /img 20 | static_dir: static/img 21 | 22 | - url: /static 23 | static_dir: static 24 | 25 | - url: /levels/0/.* 26 | script: level0.app 27 | login: required 28 | 29 | - url: /levels/1/.* 30 | script: level1.app 31 | login: required 32 | 33 | - url: /levels/2/.* 34 | script: level2.app 35 | login: required 36 | 37 | - url: /levels/3/.* 38 | script: level3.app 39 | login: required 40 | 41 | - url: /levels/4/.* 42 | script: level4.app 43 | login: required 44 | 45 | - url: .* 46 | script: main.app 47 | login: required 48 | 49 | libraries: 50 | - name: webapp2 51 | version: "2.5.2" 52 | -------------------------------------------------------------------------------- /gae/index.yaml: -------------------------------------------------------------------------------- 1 | indexes: 2 | 3 | # AUTOGENERATED 4 | 5 | # This index.yaml is automatically updated whenever the dev_appserver 6 | # detects that a new type of query is run. If you want to manage the 7 | # index.yaml file manually, remove the above marker line (the line 8 | # saying "# AUTOGENERATED"). If you want to manage some indexes 9 | # manually, move them above the marker line. The index.yaml file is 10 | # automatically uploaded to the admin console when you next deploy 11 | # your application using appcfg.py. 12 | 13 | - kind: Comment 14 | properties: 15 | - name: story 16 | - name: votes 17 | 18 | - kind: Comment 19 | properties: 20 | - name: story 21 | - name: votes 22 | direction: desc 23 | 24 | - kind: Comment 25 | properties: 26 | - name: storyKey 27 | - name: votes 28 | 29 | - kind: Post 30 | properties: 31 | - name: by 32 | - name: date 33 | direction: desc 34 | -------------------------------------------------------------------------------- /gae/level0.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import webapp2 4 | from webapp2_extras import mako 5 | from google.appengine.api import mail, users 6 | from google.appengine.ext import db 7 | 8 | class BaseHandler(webapp2.RequestHandler): 9 | @webapp2.cached_property 10 | def mako(self): 11 | return mako.get_mako(app=self.app) 12 | 13 | def render_response(self, _template, **context): 14 | rv = self.mako.render_template(_template, users=users, user=users.get_current_user(), **context) 15 | self.response.write(rv) 16 | 17 | class MainHandler(BaseHandler): 18 | def get(self): 19 | to, amount = self.request.get('to') or '', self.request.get('amount') or '' 20 | 21 | acct = sum(map(ord, users.get_current_user().nickname())) 22 | 23 | self.render_response('level0/home.html', acct_num=acct, to=to, amount=amount) 24 | 25 | def post(self): 26 | acct = sum(map(ord, users.get_current_user().nickname())) 27 | 28 | to, _from, amount = self.request.get('to'), self.request.get('from') or acct, self.request.get('amount') 29 | 30 | error = message = None 31 | if to == '' or amount == '': 32 | error = 'Missing to or amount fields.' 33 | else: 34 | try: 35 | amount, to, _from = int(amount), int(to), int(_from) 36 | except: 37 | error = 'Amount and account numbers must be integers' 38 | else: 39 | if amount > 0: 40 | message = 'Transferred $%i from %i%s to %i%s.' % (amount, _from, ' (you)' if _from == acct else '', to, ' (you)' if to == acct else '') 41 | else: 42 | error = 'Amounts must be greater than zero' 43 | 44 | self.render_response('level0/home.html', acct_num=str(acct), to='' if not error else str(to), amount='' if not error else str(amount), message=message, error=error) 45 | 46 | app = webapp2.WSGIApplication([ 47 | ('/levels/0/', MainHandler) 48 | ], debug=True) 49 | -------------------------------------------------------------------------------- /gae/level1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import datetime, hashlib, re, webapp2 4 | from webapp2_extras import mako 5 | from google.appengine.api import mail, users 6 | from google.appengine.ext import db 7 | 8 | class Post(db.Model): 9 | by = db.UserProperty() 10 | contents = db.TextProperty() 11 | date = db.DateTimeProperty() 12 | 13 | class BaseHandler(webapp2.RequestHandler): 14 | @webapp2.cached_property 15 | def mako(self): 16 | return mako.get_mako(app=self.app) 17 | 18 | def render_response(self, _template, **context): 19 | rv = self.mako.render_template(_template, users=users, user=users.get_current_user(), **context) 20 | self.response.write(rv) 21 | 22 | class MainHandler(BaseHandler): 23 | def get(self): 24 | csrf = hashlib.md5(users.get_current_user().nickname()).hexdigest() 25 | allposts = Post.all().order('date') 26 | posts = [] 27 | for i, post in enumerate(allposts): 28 | if post.by == users.get_current_user(): 29 | posts.append((i, post)) 30 | posts.reverse() 31 | self.render_response('level1/home.html', csrf=csrf, posts=posts) 32 | 33 | class PostHandler(BaseHandler): 34 | def get(self): 35 | id = self.request.get('id') 36 | 37 | self.render_response('level1/post.html', post=Post.all().order('date')[int(id)]) 38 | 39 | def post(self): 40 | if len(self.request.get('csrf')) != 32: 41 | self.response.write('Bad CSRF token') 42 | return 43 | 44 | status = ostatus = self.request.get('status') 45 | status = status.replace('<', '<').replace('>', '>').replace('\n', '
') 46 | message = [None] 47 | 48 | def rep(match): 49 | if '>' in match.group(0): 50 | message[0] = "So, you can't break out of the tag. But what can you do inside the tag?" 51 | return '%s' % (match.group(0), match.group(0)) 52 | status = re.sub(r'http://\S+', rep, status) 53 | 54 | Post(by=users.get_current_user(), contents=status, date=datetime.datetime.now()).put() 55 | 56 | self.render_response('level1/posted.html', message=message[0]) 57 | 58 | app = webapp2.WSGIApplication([ 59 | ('/levels/1/', MainHandler), 60 | ('/levels/1/post', PostHandler), 61 | ], debug=True) 62 | -------------------------------------------------------------------------------- /gae/level2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import datetime, hashlib, random, re, webapp2 4 | from webapp2_extras import mako, sessions 5 | from google.appengine.api import mail, users 6 | from google.appengine.ext import db 7 | 8 | class Profile(db.Model): 9 | user = db.UserProperty() 10 | nickname = db.TextProperty() 11 | desc = db.TextProperty() 12 | pic = db.TextProperty() 13 | 14 | def html_desc(self): 15 | def rep(match): 16 | sub = match.group(1) 17 | if '|' not in sub: 18 | return '[' + sub + ']' 19 | else: 20 | color, text = sub.split('|', 1) 21 | color = color.replace('<', '\0') 22 | color = color.replace('>', '\x02') 23 | color = color.replace('"', '\x01') 24 | return '\0span style=\x01color: %s\x01\x02%s\x00/span\x02' % (color.strip(), text.strip()) 25 | desc = re.sub(r'\[(.*?)\]', rep, self.desc) 26 | desc = desc.replace('<', '<') 27 | desc = desc.replace('>', '>') 28 | desc = desc.replace('"', '"') 29 | desc = desc.replace('\0', '<') 30 | desc = desc.replace('\x02', '>') 31 | desc = desc.replace('\x01', '"') 32 | desc = desc.replace('\n', '
') 33 | return desc 34 | 35 | class BaseHandler(webapp2.RequestHandler): 36 | @webapp2.cached_property 37 | def mako(self): 38 | return mako.get_mako(app=self.app) 39 | 40 | def dispatch(self): 41 | self.session_store = sessions.get_store(request=self.request) 42 | 43 | try: 44 | webapp2.RequestHandler.dispatch(self) 45 | finally: 46 | self.session_store.save_sessions(self.response) 47 | 48 | @webapp2.cached_property 49 | def session(self): 50 | return self.session_store.get_session() 51 | 52 | @property 53 | def csrf(self): 54 | csrf = self.session.get('csrf', None) 55 | if csrf == None: 56 | csrf = ''.join('%02x' % ord(c) for c in '...Not this time!' + ''.join(chr(random.randrange(256)) for i in xrange(16))) 57 | self.session['csrf'] = csrf 58 | return csrf 59 | 60 | def render_response(self, _template, **context): 61 | rv = self.mako.render_template(_template, users=users, user=users.get_current_user(), csrf=self.csrf, **context) 62 | self.response.write(rv) 63 | 64 | class MainHandler(BaseHandler): 65 | def get(self): 66 | id = self.request.get('id') 67 | if id: 68 | try: 69 | profile = Profile.get_by_id(int(id)) 70 | except: 71 | profile = None 72 | if profile == None: 73 | self.response.write('Unknown ID ' + id) 74 | return 75 | else: 76 | profiles = list(Profile.all().filter('user =', users.get_current_user()).run()) 77 | if len(profiles) == 0: 78 | profile = Profile( 79 | user=users.get_current_user(), 80 | nickname=users.get_current_user().nickname(), 81 | desc='[ red | All ] [ orange | the ] [ yellow | colors ]\n[ green | of ] [ blue | the ] [ purple | rainbow! ]', 82 | pic='http://breaker-studentcenter.appspot.com/favicon.png' 83 | ) 84 | profile.put() 85 | else: 86 | profile = profiles[0] 87 | 88 | self.render_response('level2/home.html', profile=profile, editable=profile.user == users.get_current_user()) 89 | 90 | class EditHandler(BaseHandler): 91 | def get(self): 92 | profile = list(Profile.all().filter('user =', users.get_current_user()).run())[0] 93 | 94 | self.render_response('level2/edit.html', profile=profile, id=self.request.get('id')) 95 | 96 | def post(self): 97 | if self.request.get('csrf') != self.csrf: 98 | self.response.write('Bad CSRF token') 99 | return 100 | 101 | pic = self.request.get('pic') 102 | if not '.' in pic or pic.rsplit('.', 1)[1].lower() not in ('png', 'jpg', 'jpeg', 'ico'): 103 | self.response.write('Profile picture must be PNG, JPG, or ICO!') 104 | return 105 | 106 | profile = list(Profile.all().filter('user =', users.get_current_user()).run())[0] 107 | profile.nickname = self.request.get('nickname') 108 | profile.pic = pic 109 | profile.desc = self.request.get('desc') 110 | profile.put() 111 | 112 | self.redirect('/levels/2/') 113 | 114 | config = {} 115 | config['webapp2_extras.sessions'] = { 116 | 'secret_key' : 'firstclass' 117 | } 118 | 119 | app = webapp2.WSGIApplication([ 120 | ('/levels/2/', MainHandler), 121 | ('/levels/2/edit', EditHandler), 122 | ], debug=True, config=config) 123 | -------------------------------------------------------------------------------- /gae/level3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import datetime, hashlib, random, re, webapp2 4 | from webapp2_extras import mako, sessions 5 | from google.appengine.api import mail, users 6 | from google.appengine.ext import db 7 | 8 | def filter_tags(text): 9 | def rep(match): 10 | text = match.group(1) 11 | if text[0] == '/': 12 | return match.group(0) 13 | else: 14 | if text.startswith('script'): 15 | return 'JS Detected!' 16 | elif text.startswith('a '): 17 | if len(re.findall(r'\Won\w+=', text)): 18 | return 'JS Detected!' 19 | else: 20 | return match.group(0) 21 | else: 22 | return match.group(0) 23 | return re.sub(r'<(.*?)>', rep, text) 24 | 25 | class Page(db.Model): 26 | user = db.UserProperty() 27 | title = db.TextProperty() 28 | body = db.TextProperty() 29 | 30 | @property 31 | def html_body(self): 32 | return filter_tags(self.body).replace('\n', '
') 33 | 34 | class BaseHandler(webapp2.RequestHandler): 35 | @webapp2.cached_property 36 | def mako(self): 37 | return mako.get_mako(app=self.app) 38 | 39 | def dispatch(self): 40 | self.session_store = sessions.get_store(request=self.request) 41 | 42 | try: 43 | webapp2.RequestHandler.dispatch(self) 44 | finally: 45 | self.session_store.save_sessions(self.response) 46 | 47 | @webapp2.cached_property 48 | def session(self): 49 | return self.session_store.get_session() 50 | 51 | @property 52 | def csrf(self): 53 | csrf = self.session.get('csrf', None) 54 | if csrf == None: 55 | csrf = ''.join('%02x' % ord(c) for c in '...Not this time!' + ''.join(chr(random.randrange(256)) for i in xrange(16))) 56 | self.session['csrf'] = csrf 57 | return csrf 58 | 59 | def render_response(self, _template, **context): 60 | rv = self.mako.render_template(_template, users=users, user=users.get_current_user(), csrf=self.csrf, **context) 61 | self.response.write(rv) 62 | 63 | class MainHandler(BaseHandler): 64 | def get(self): 65 | pages = list(Page.all().filter('user =', users.get_current_user()).run()) 66 | if len(pages) == 0: 67 | page = Page( 68 | user=users.get_current_user(), 69 | title='Welcome to Breaker CMS!', 70 | body='No content yet. Log in as an admin to change it!' 71 | ) 72 | page.put() 73 | else: 74 | page = pages[0] 75 | if self.request.cookies.get('admin', None) == None: 76 | self.response.set_cookie('admin', '0') 77 | self.render_response('level3/home.html', page=page) 78 | 79 | class AdminHandler(BaseHandler): 80 | def get(self): 81 | if self.request.cookies.get('admin') != '1': 82 | self.response.write('Not an admin!') 83 | return 84 | page = list(Page.all().filter('user =', users.get_current_user()).run())[0] 85 | self.render_response('level3/admin.html', page=page) 86 | 87 | def post(self): 88 | page = list(Page.all().filter('user =', users.get_current_user()).run())[0] 89 | page.title = self.request.get('title') 90 | page.body = self.request.get('body') 91 | page.put() 92 | self.redirect('/levels/3/') 93 | 94 | config = {} 95 | config['webapp2_extras.sessions'] = { 96 | 'secret_key' : 'posjdapfosdjfpdsoafjsdpofjdspvompmepofmpofj4pfowjfpojcpo4pomapojm' 97 | } 98 | 99 | app = webapp2.WSGIApplication([ 100 | ('/levels/3/', MainHandler), 101 | ('/levels/3/admin', AdminHandler), 102 | ], debug=True, config=config) 103 | -------------------------------------------------------------------------------- /gae/level4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import datetime, hashlib, random, re, webapp2 4 | from webapp2_extras import mako, sessions 5 | from google.appengine.api import mail, users 6 | from google.appengine.ext import db 7 | 8 | class Story(db.Model): 9 | user = db.UserProperty() 10 | title = db.TextProperty() 11 | link = db.TextProperty() 12 | votes = db.IntegerProperty() 13 | 14 | @property 15 | def domain(self): 16 | return '.'.join(self.link.split('://', 1)[1].split('/', 1)[0].rsplit('.', 2)[-2:]) 17 | 18 | @property 19 | def comments(self): 20 | return list(Comment.all().filter('story =', self).order('-votes').run()) 21 | 22 | class User(db.Model): 23 | user = db.UserProperty() 24 | karma = db.IntegerProperty() 25 | votes = db.ListProperty(long) 26 | 27 | def voted_on(self, id): 28 | return id in self.votes 29 | 30 | class Comment(db.Model): 31 | user = db.UserProperty() 32 | votes = db.IntegerProperty() 33 | content = db.TextProperty() 34 | story = db.ReferenceProperty(Story) 35 | 36 | class BaseHandler(webapp2.RequestHandler): 37 | @webapp2.cached_property 38 | def mako(self): 39 | return mako.get_mako(app=self.app) 40 | 41 | def dispatch(self): 42 | dusers = list(User.all().filter('user =', users.get_current_user()).run()) 43 | if len(dusers): 44 | self.dbuser = dusers[0] 45 | else: 46 | self.dbuser = User(user=users.get_current_user(), karma=0) 47 | self.dbuser.put() 48 | 49 | self.session_store = sessions.get_store(request=self.request) 50 | 51 | try: 52 | webapp2.RequestHandler.dispatch(self) 53 | finally: 54 | self.session_store.save_sessions(self.response) 55 | 56 | @webapp2.cached_property 57 | def session(self): 58 | return self.session_store.get_session() 59 | 60 | @property 61 | def csrf(self): 62 | csrf = self.session.get('csrf', None) 63 | if csrf == None: 64 | csrf = ''.join('%02x' % ord(c) for c in '...Not this time!' + ''.join(chr(random.randrange(256)) for i in xrange(16))) 65 | self.session['csrf'] = csrf 66 | return csrf 67 | 68 | def render_response(self, _template, **context): 69 | rv = self.mako.render_template(_template, page_url=self.request.url, users=users, user=users.get_current_user(), dbuser=self.dbuser, csrf=self.csrf, **context) 70 | self.response.write(rv) 71 | 72 | class MainHandler(BaseHandler): 73 | def get(self): 74 | stories = list(Story.all().run()) 75 | self.render_response('level4/home.html', stories=stories, curuser=self.dbuser) 76 | 77 | class SubmitHandler(BaseHandler): 78 | def get(self): 79 | self.render_response('level4/submit.html', curuser=self.dbuser) 80 | 81 | def post(self): 82 | if self.request.get('csrf') != self.csrf: 83 | self.response.write('Invalid CSRF token!') 84 | return 85 | 86 | link = self.request.get('link') 87 | if not link.lower().startswith('http://') and not link.lower().startswith('https://'): 88 | self.response.write('Link must be http or https.') 89 | return 90 | 91 | story = Story( 92 | user=users.get_current_user(), 93 | title=self.request.get('title')[:80], 94 | link=link, 95 | votes=1 96 | ) 97 | story.put() 98 | 99 | self.redirect('/levels/4/') 100 | 101 | class VoteHandler(BaseHandler): 102 | def get(self): 103 | id = int(self.request.get('id')) 104 | type = globals()[self.request.get('type')] 105 | obj = type.get_by_id(id) 106 | 107 | if self.dbuser.voted_on(id): 108 | self.redirect(str(self.request.get('from'))) 109 | return 110 | 111 | self.dbuser.votes.append(id) 112 | self.dbuser.put() 113 | 114 | if type == Story: 115 | change = 1 116 | else: 117 | change = int(self.request.get('change')) 118 | if change < 0: 119 | change = -1 120 | else: 121 | change = 1 122 | 123 | obj.votes += change 124 | obj.put() 125 | user = list(User.all().filter('user =', obj.user).run())[0] 126 | user.karma += change 127 | user.put() 128 | 129 | self.redirect(str(self.request.get('from'))) 130 | 131 | class DeleteHandler(BaseHandler): 132 | def get(self): 133 | self.render_response('level4/delete.html', curuser=self.dbuser, id=self.request.get('id'), type=self.request.get('type'), _from=self.request.get('from')) 134 | 135 | def post(self): 136 | id = int(self.request.get('id')) 137 | type = globals()[self.request.get('type')] 138 | obj = type.get_by_id(id) 139 | 140 | if obj.user != users.get_current_user(): 141 | self.response.write("Cannot delete other users' posts") 142 | return 143 | 144 | obj.delete() 145 | 146 | if type == Story and '/comments?' in self.request.get('from'): 147 | self.redirect('/levels/4/') 148 | else: 149 | self.redirect(str(self.request.get('from'))) 150 | 151 | class CommentHandler(BaseHandler): 152 | def get(self): 153 | story = Story.get_by_id(int(self.request.get('id'))) 154 | self.render_response('level4/comments.html', curuser=self.dbuser, story=story) 155 | 156 | def post(self): 157 | if self.request.get('csrf') != self.csrf: 158 | self.response.write('Mismatched CSRF token') 159 | return 160 | 161 | id = self.request.get('id') 162 | comment = Comment( 163 | user=users.get_current_user(), 164 | story=Story.get_by_id(int(id)), 165 | votes=1, 166 | content=self.request.get('comment') 167 | ) 168 | comment.put() 169 | self.redirect('/levels/4/comments?id=' + id) 170 | 171 | config = {} 172 | config['webapp2_extras.sessions'] = { 173 | 'secret_key' : 'paodsjfpsdojfasdpofjsdpfosdjafpwme;lcme;aclm;jp;avra' 174 | } 175 | 176 | app = webapp2.WSGIApplication([ 177 | ('/levels/4/', MainHandler), 178 | ('/levels/4/submit', SubmitHandler), 179 | ('/levels/4/vote', VoteHandler), 180 | ('/levels/4/comments', CommentHandler), 181 | ('/levels/4/delete', DeleteHandler), 182 | ], debug=True, config=config) 183 | -------------------------------------------------------------------------------- /gae/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import webapp2 4 | from webapp2_extras import mako 5 | from google.appengine.api import mail, users 6 | from google.appengine.ext import db 7 | 8 | class BaseHandler(webapp2.RequestHandler): 9 | @webapp2.cached_property 10 | def mako(self): 11 | return mako.get_mako(app=self.app) 12 | 13 | def render_response(self, _template, **context): 14 | rv = self.mako.render_template(_template, users=users, user=users.get_current_user(), **context) 15 | self.response.write(rv) 16 | 17 | class MainHandler(BaseHandler): 18 | def get(self): 19 | self.render_response('home.html') 20 | 21 | app = webapp2.WSGIApplication([ 22 | ('/', MainHandler) 23 | ], debug=True) 24 | -------------------------------------------------------------------------------- /gae/mako/__init__.py: -------------------------------------------------------------------------------- 1 | # mako/__init__.py 2 | # Copyright (C) 2006-2012 the Mako authors and contributors 3 | # 4 | # This module is part of Mako and is released under 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 6 | 7 | 8 | __version__ = '0.8.1' 9 | 10 | -------------------------------------------------------------------------------- /gae/mako/ast.py: -------------------------------------------------------------------------------- 1 | # mako/ast.py 2 | # Copyright (C) 2006-2012 the Mako authors and contributors 3 | # 4 | # This module is part of Mako and is released under 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 6 | 7 | """utilities for analyzing expressions and blocks of Python 8 | code, as well as generating Python from AST nodes""" 9 | 10 | from mako import exceptions, pyparser, compat 11 | import re 12 | 13 | class PythonCode(object): 14 | """represents information about a string containing Python code""" 15 | def __init__(self, code, **exception_kwargs): 16 | self.code = code 17 | 18 | # represents all identifiers which are assigned to at some point in 19 | # the code 20 | self.declared_identifiers = set() 21 | 22 | # represents all identifiers which are referenced before their 23 | # assignment, if any 24 | self.undeclared_identifiers = set() 25 | 26 | # note that an identifier can be in both the undeclared and declared 27 | # lists. 28 | 29 | # using AST to parse instead of using code.co_varnames, 30 | # code.co_names has several advantages: 31 | # - we can locate an identifier as "undeclared" even if 32 | # its declared later in the same block of code 33 | # - AST is less likely to break with version changes 34 | # (for example, the behavior of co_names changed a little bit 35 | # in python version 2.5) 36 | if isinstance(code, compat.string_types): 37 | expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs) 38 | else: 39 | expr = code 40 | 41 | f = pyparser.FindIdentifiers(self, **exception_kwargs) 42 | f.visit(expr) 43 | 44 | class ArgumentList(object): 45 | """parses a fragment of code as a comma-separated list of expressions""" 46 | def __init__(self, code, **exception_kwargs): 47 | self.codeargs = [] 48 | self.args = [] 49 | self.declared_identifiers = set() 50 | self.undeclared_identifiers = set() 51 | if isinstance(code, compat.string_types): 52 | if re.match(r"\S", code) and not re.match(r",\s*$", code): 53 | # if theres text and no trailing comma, insure its parsed 54 | # as a tuple by adding a trailing comma 55 | code += "," 56 | expr = pyparser.parse(code, "exec", **exception_kwargs) 57 | else: 58 | expr = code 59 | 60 | f = pyparser.FindTuple(self, PythonCode, **exception_kwargs) 61 | f.visit(expr) 62 | 63 | class PythonFragment(PythonCode): 64 | """extends PythonCode to provide identifier lookups in partial control 65 | statements 66 | 67 | e.g. 68 | for x in 5: 69 | elif y==9: 70 | except (MyException, e): 71 | etc. 72 | """ 73 | def __init__(self, code, **exception_kwargs): 74 | m = re.match(r'^(\w+)(?:\s+(.*?))?:\s*(#|$)', code.strip(), re.S) 75 | if not m: 76 | raise exceptions.CompileException( 77 | "Fragment '%s' is not a partial control statement" % 78 | code, **exception_kwargs) 79 | if m.group(3): 80 | code = code[:m.start(3)] 81 | (keyword, expr) = m.group(1,2) 82 | if keyword in ['for','if', 'while']: 83 | code = code + "pass" 84 | elif keyword == 'try': 85 | code = code + "pass\nexcept:pass" 86 | elif keyword == 'elif' or keyword == 'else': 87 | code = "if False:pass\n" + code + "pass" 88 | elif keyword == 'except': 89 | code = "try:pass\n" + code + "pass" 90 | elif keyword == 'with': 91 | code = code + "pass" 92 | else: 93 | raise exceptions.CompileException( 94 | "Unsupported control keyword: '%s'" % 95 | keyword, **exception_kwargs) 96 | super(PythonFragment, self).__init__(code, **exception_kwargs) 97 | 98 | 99 | class FunctionDecl(object): 100 | """function declaration""" 101 | def __init__(self, code, allow_kwargs=True, **exception_kwargs): 102 | self.code = code 103 | expr = pyparser.parse(code, "exec", **exception_kwargs) 104 | 105 | f = pyparser.ParseFunc(self, **exception_kwargs) 106 | f.visit(expr) 107 | if not hasattr(self, 'funcname'): 108 | raise exceptions.CompileException( 109 | "Code '%s' is not a function declaration" % code, 110 | **exception_kwargs) 111 | if not allow_kwargs and self.kwargs: 112 | raise exceptions.CompileException( 113 | "'**%s' keyword argument not allowed here" % 114 | self.argnames[-1], **exception_kwargs) 115 | 116 | def get_argument_expressions(self, include_defaults=True): 117 | """return the argument declarations of this FunctionDecl as a printable 118 | list.""" 119 | 120 | namedecls = [] 121 | defaults = [d for d in self.defaults] 122 | kwargs = self.kwargs 123 | varargs = self.varargs 124 | argnames = [f for f in self.argnames] 125 | argnames.reverse() 126 | for arg in argnames: 127 | default = None 128 | if kwargs: 129 | arg = "**" + arg 130 | kwargs = False 131 | elif varargs: 132 | arg = "*" + arg 133 | varargs = False 134 | else: 135 | default = len(defaults) and defaults.pop() or None 136 | if include_defaults and default: 137 | namedecls.insert(0, "%s=%s" % 138 | (arg, 139 | pyparser.ExpressionGenerator(default).value() 140 | ) 141 | ) 142 | else: 143 | namedecls.insert(0, arg) 144 | return namedecls 145 | 146 | class FunctionArgs(FunctionDecl): 147 | """the argument portion of a function declaration""" 148 | 149 | def __init__(self, code, **kwargs): 150 | super(FunctionArgs, self).__init__("def ANON(%s):pass" % code, 151 | **kwargs) 152 | -------------------------------------------------------------------------------- /gae/mako/cache.py: -------------------------------------------------------------------------------- 1 | # mako/cache.py 2 | # Copyright (C) 2006-2012 the Mako authors and contributors 3 | # 4 | # This module is part of Mako and is released under 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 6 | 7 | from mako import compat, util 8 | 9 | _cache_plugins = util.PluginLoader("mako.cache") 10 | 11 | register_plugin = _cache_plugins.register 12 | register_plugin("beaker", "mako.ext.beaker_cache", "BeakerCacheImpl") 13 | 14 | 15 | class Cache(object): 16 | """Represents a data content cache made available to the module 17 | space of a specific :class:`.Template` object. 18 | 19 | .. versionadded:: 0.6 20 | :class:`.Cache` by itself is mostly a 21 | container for a :class:`.CacheImpl` object, which implements 22 | a fixed API to provide caching services; specific subclasses exist to 23 | implement different 24 | caching strategies. Mako includes a backend that works with 25 | the Beaker caching system. Beaker itself then supports 26 | a number of backends (i.e. file, memory, memcached, etc.) 27 | 28 | The construction of a :class:`.Cache` is part of the mechanics 29 | of a :class:`.Template`, and programmatic access to this 30 | cache is typically via the :attr:`.Template.cache` attribute. 31 | 32 | """ 33 | 34 | impl = None 35 | """Provide the :class:`.CacheImpl` in use by this :class:`.Cache`. 36 | 37 | This accessor allows a :class:`.CacheImpl` with additional 38 | methods beyond that of :class:`.Cache` to be used programmatically. 39 | 40 | """ 41 | 42 | id = None 43 | """Return the 'id' that identifies this cache. 44 | 45 | This is a value that should be globally unique to the 46 | :class:`.Template` associated with this cache, and can 47 | be used by a caching system to name a local container 48 | for data specific to this template. 49 | 50 | """ 51 | 52 | starttime = None 53 | """Epochal time value for when the owning :class:`.Template` was 54 | first compiled. 55 | 56 | A cache implementation may wish to invalidate data earlier than 57 | this timestamp; this has the effect of the cache for a specific 58 | :class:`.Template` starting clean any time the :class:`.Template` 59 | is recompiled, such as when the original template file changed on 60 | the filesystem. 61 | 62 | """ 63 | 64 | def __init__(self, template, *args): 65 | # check for a stale template calling the 66 | # constructor 67 | if isinstance(template, compat.string_types) and args: 68 | return 69 | self.template = template 70 | self.id = template.module.__name__ 71 | self.starttime = template.module._modified_time 72 | self._def_regions = {} 73 | self.impl = self._load_impl(self.template.cache_impl) 74 | 75 | def _load_impl(self, name): 76 | return _cache_plugins.load(name)(self) 77 | 78 | def get_or_create(self, key, creation_function, **kw): 79 | """Retrieve a value from the cache, using the given creation function 80 | to generate a new value.""" 81 | 82 | return self._ctx_get_or_create(key, creation_function, None, **kw) 83 | 84 | def _ctx_get_or_create(self, key, creation_function, context, **kw): 85 | """Retrieve a value from the cache, using the given creation function 86 | to generate a new value.""" 87 | 88 | if not self.template.cache_enabled: 89 | return creation_function() 90 | 91 | return self.impl.get_or_create(key, 92 | creation_function, 93 | **self._get_cache_kw(kw, context)) 94 | 95 | def set(self, key, value, **kw): 96 | """Place a value in the cache. 97 | 98 | :param key: the value's key. 99 | :param value: the value. 100 | :param \**kw: cache configuration arguments. 101 | 102 | """ 103 | 104 | self.impl.set(key, value, **self._get_cache_kw(kw, None)) 105 | 106 | put = set 107 | """A synonym for :meth:`.Cache.set`. 108 | 109 | This is here for backwards compatibility. 110 | 111 | """ 112 | 113 | def get(self, key, **kw): 114 | """Retrieve a value from the cache. 115 | 116 | :param key: the value's key. 117 | :param \**kw: cache configuration arguments. The 118 | backend is configured using these arguments upon first request. 119 | Subsequent requests that use the same series of configuration 120 | values will use that same backend. 121 | 122 | """ 123 | return self.impl.get(key, **self._get_cache_kw(kw, None)) 124 | 125 | def invalidate(self, key, **kw): 126 | """Invalidate a value in the cache. 127 | 128 | :param key: the value's key. 129 | :param \**kw: cache configuration arguments. The 130 | backend is configured using these arguments upon first request. 131 | Subsequent requests that use the same series of configuration 132 | values will use that same backend. 133 | 134 | """ 135 | self.impl.invalidate(key, **self._get_cache_kw(kw, None)) 136 | 137 | def invalidate_body(self): 138 | """Invalidate the cached content of the "body" method for this 139 | template. 140 | 141 | """ 142 | self.invalidate('render_body', __M_defname='render_body') 143 | 144 | def invalidate_def(self, name): 145 | """Invalidate the cached content of a particular ``<%def>`` within this 146 | template. 147 | 148 | """ 149 | 150 | self.invalidate('render_%s' % name, __M_defname='render_%s' % name) 151 | 152 | def invalidate_closure(self, name): 153 | """Invalidate a nested ``<%def>`` within this template. 154 | 155 | Caching of nested defs is a blunt tool as there is no 156 | management of scope -- nested defs that use cache tags 157 | need to have names unique of all other nested defs in the 158 | template, else their content will be overwritten by 159 | each other. 160 | 161 | """ 162 | 163 | self.invalidate(name, __M_defname=name) 164 | 165 | def _get_cache_kw(self, kw, context): 166 | defname = kw.pop('__M_defname', None) 167 | if not defname: 168 | tmpl_kw = self.template.cache_args.copy() 169 | tmpl_kw.update(kw) 170 | elif defname in self._def_regions: 171 | tmpl_kw = self._def_regions[defname] 172 | else: 173 | tmpl_kw = self.template.cache_args.copy() 174 | tmpl_kw.update(kw) 175 | self._def_regions[defname] = tmpl_kw 176 | if context and self.impl.pass_context: 177 | tmpl_kw = tmpl_kw.copy() 178 | tmpl_kw.setdefault('context', context) 179 | return tmpl_kw 180 | 181 | class CacheImpl(object): 182 | """Provide a cache implementation for use by :class:`.Cache`.""" 183 | 184 | def __init__(self, cache): 185 | self.cache = cache 186 | 187 | pass_context = False 188 | """If ``True``, the :class:`.Context` will be passed to 189 | :meth:`get_or_create <.CacheImpl.get_or_create>` as the name ``'context'``. 190 | """ 191 | 192 | def get_or_create(self, key, creation_function, **kw): 193 | """Retrieve a value from the cache, using the given creation function 194 | to generate a new value. 195 | 196 | This function *must* return a value, either from 197 | the cache, or via the given creation function. 198 | If the creation function is called, the newly 199 | created value should be populated into the cache 200 | under the given key before being returned. 201 | 202 | :param key: the value's key. 203 | :param creation_function: function that when called generates 204 | a new value. 205 | :param \**kw: cache configuration arguments. 206 | 207 | """ 208 | raise NotImplementedError() 209 | 210 | def set(self, key, value, **kw): 211 | """Place a value in the cache. 212 | 213 | :param key: the value's key. 214 | :param value: the value. 215 | :param \**kw: cache configuration arguments. 216 | 217 | """ 218 | raise NotImplementedError() 219 | 220 | def get(self, key, **kw): 221 | """Retrieve a value from the cache. 222 | 223 | :param key: the value's key. 224 | :param \**kw: cache configuration arguments. 225 | 226 | """ 227 | raise NotImplementedError() 228 | 229 | def invalidate(self, key, **kw): 230 | """Invalidate a value in the cache. 231 | 232 | :param key: the value's key. 233 | :param \**kw: cache configuration arguments. 234 | 235 | """ 236 | raise NotImplementedError() 237 | -------------------------------------------------------------------------------- /gae/mako/compat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | 4 | py3k = sys.version_info >= (3, 0) 5 | py33 = sys.version_info >= (3, 3) 6 | py26 = sys.version_info >= (2, 6) 7 | py25 = sys.version_info >= (2, 5) 8 | jython = sys.platform.startswith('java') 9 | win32 = sys.platform.startswith('win') 10 | pypy = hasattr(sys, 'pypy_version_info') 11 | 12 | if py3k: 13 | from io import StringIO 14 | import builtins as compat_builtins 15 | from urllib.parse import quote_plus, unquote_plus 16 | from html.entities import codepoint2name, name2codepoint 17 | string_types = str, 18 | binary_type = bytes 19 | text_type = str 20 | 21 | def u(s): 22 | return s 23 | 24 | def octal(lit): 25 | return eval("0o" + lit) 26 | 27 | else: 28 | import __builtin__ as compat_builtins 29 | try: 30 | from cStringIO import StringIO 31 | except: 32 | from StringIO import StringIO 33 | from urllib import quote_plus, unquote_plus 34 | from htmlentitydefs import codepoint2name, name2codepoint 35 | string_types = basestring, 36 | binary_type = str 37 | text_type = unicode 38 | 39 | def u(s): 40 | return unicode(s, "utf-8") 41 | 42 | def octal(lit): 43 | return eval("0" + lit) 44 | 45 | 46 | if py33: 47 | from importlib import machinery 48 | def load_module(module_id, path): 49 | return machinery.SourceFileLoader(module_id, path).load_module() 50 | else: 51 | import imp 52 | def load_module(module_id, path): 53 | fp = open(path, 'rb') 54 | try: 55 | return imp.load_source(module_id, path, fp) 56 | finally: 57 | fp.close() 58 | 59 | 60 | def exception_as(): 61 | return sys.exc_info()[1] 62 | 63 | try: 64 | import threading 65 | if py3k: 66 | import _thread as thread 67 | else: 68 | import thread 69 | except ImportError: 70 | import dummy_threading as threading 71 | if py3k: 72 | import _dummy_thread as thread 73 | else: 74 | import dummy_thread as thread 75 | 76 | if win32 or jython: 77 | time_func = time.clock 78 | else: 79 | time_func = time.time 80 | 81 | try: 82 | from functools import partial 83 | except: 84 | def partial(func, *args, **keywords): 85 | def newfunc(*fargs, **fkeywords): 86 | newkeywords = keywords.copy() 87 | newkeywords.update(fkeywords) 88 | return func(*(args + fargs), **newkeywords) 89 | return newfunc 90 | 91 | if not py25: 92 | def all(iterable): 93 | for i in iterable: 94 | if not i: 95 | return False 96 | return True 97 | 98 | def exception_name(exc): 99 | try: 100 | return exc.__class__.__name__ 101 | except AttributeError: 102 | return exc.__name__ 103 | else: 104 | all = all 105 | 106 | def exception_name(exc): 107 | return exc.__class__.__name__ 108 | 109 | try: 110 | from inspect import CO_VARKEYWORDS, CO_VARARGS 111 | def inspect_func_args(fn): 112 | if py3k: 113 | co = fn.__code__ 114 | else: 115 | co = fn.func_code 116 | 117 | nargs = co.co_argcount 118 | names = co.co_varnames 119 | args = list(names[:nargs]) 120 | 121 | varargs = None 122 | if co.co_flags & CO_VARARGS: 123 | varargs = co.co_varnames[nargs] 124 | nargs = nargs + 1 125 | varkw = None 126 | if co.co_flags & CO_VARKEYWORDS: 127 | varkw = co.co_varnames[nargs] 128 | 129 | if py3k: 130 | return args, varargs, varkw, fn.__defaults__ 131 | else: 132 | return args, varargs, varkw, fn.func_defaults 133 | except ImportError: 134 | import inspect 135 | def inspect_func_args(fn): 136 | return inspect.getargspec(fn) 137 | 138 | if py3k: 139 | def callable(fn): 140 | return hasattr(fn, '__call__') 141 | else: 142 | callable = callable 143 | 144 | 145 | ################################################ 146 | # cross-compatible metaclass implementation 147 | # Copyright (c) 2010-2012 Benjamin Peterson 148 | def with_metaclass(meta, base=object): 149 | """Create a base class with a metaclass.""" 150 | return meta("%sBase" % meta.__name__, (base,), {}) 151 | ################################################ 152 | 153 | -------------------------------------------------------------------------------- /gae/mako/exceptions.py: -------------------------------------------------------------------------------- 1 | # mako/exceptions.py 2 | # Copyright (C) 2006-2012 the Mako authors and contributors 3 | # 4 | # This module is part of Mako and is released under 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 6 | 7 | """exception classes""" 8 | 9 | import traceback 10 | import sys 11 | import re 12 | from mako import util, compat 13 | 14 | class MakoException(Exception): 15 | pass 16 | 17 | class RuntimeException(MakoException): 18 | pass 19 | 20 | def _format_filepos(lineno, pos, filename): 21 | if filename is None: 22 | return " at line: %d char: %d" % (lineno, pos) 23 | else: 24 | return " in file '%s' at line: %d char: %d" % (filename, lineno, pos) 25 | 26 | 27 | class CompileException(MakoException): 28 | def __init__(self, message, source, lineno, pos, filename): 29 | MakoException.__init__(self, 30 | message + _format_filepos(lineno, pos, filename)) 31 | self.lineno =lineno 32 | self.pos = pos 33 | self.filename = filename 34 | self.source = source 35 | 36 | class SyntaxException(MakoException): 37 | def __init__(self, message, source, lineno, pos, filename): 38 | MakoException.__init__(self, 39 | message + _format_filepos(lineno, pos, filename)) 40 | self.lineno =lineno 41 | self.pos = pos 42 | self.filename = filename 43 | self.source = source 44 | 45 | class UnsupportedError(MakoException): 46 | """raised when a retired feature is used.""" 47 | 48 | class NameConflictError(MakoException): 49 | """raised when a reserved word is used inappropriately""" 50 | 51 | class TemplateLookupException(MakoException): 52 | pass 53 | 54 | class TopLevelLookupException(TemplateLookupException): 55 | pass 56 | 57 | class RichTraceback(object): 58 | """Pull the current exception from the ``sys`` traceback and extracts 59 | Mako-specific template information. 60 | 61 | See the usage examples in :ref:`handling_exceptions`. 62 | 63 | """ 64 | def __init__(self, error=None, traceback=None): 65 | self.source, self.lineno = "", 0 66 | 67 | if error is None or traceback is None: 68 | t, value, tback = sys.exc_info() 69 | 70 | if error is None: 71 | error = value or t 72 | 73 | if traceback is None: 74 | traceback = tback 75 | 76 | self.error = error 77 | self.records = self._init(traceback) 78 | 79 | if isinstance(self.error, (CompileException, SyntaxException)): 80 | import mako.template 81 | self.source = self.error.source 82 | self.lineno = self.error.lineno 83 | self._has_source = True 84 | 85 | self._init_message() 86 | 87 | @property 88 | def errorname(self): 89 | return compat.exception_name(self.error) 90 | 91 | def _init_message(self): 92 | """Find a unicode representation of self.error""" 93 | try: 94 | self.message = compat.text_type(self.error) 95 | except UnicodeError: 96 | try: 97 | self.message = str(self.error) 98 | except UnicodeEncodeError: 99 | # Fallback to args as neither unicode nor 100 | # str(Exception(u'\xe6')) work in Python < 2.6 101 | self.message = self.error.args[0] 102 | if not isinstance(self.message, compat.text_type): 103 | self.message = compat.text_type(self.message, 'ascii', 'replace') 104 | 105 | def _get_reformatted_records(self, records): 106 | for rec in records: 107 | if rec[6] is not None: 108 | yield (rec[4], rec[5], rec[2], rec[6]) 109 | else: 110 | yield tuple(rec[0:4]) 111 | 112 | @property 113 | def traceback(self): 114 | """Return a list of 4-tuple traceback records (i.e. normal python 115 | format) with template-corresponding lines remapped to the originating 116 | template. 117 | 118 | """ 119 | return list(self._get_reformatted_records(self.records)) 120 | 121 | @property 122 | def reverse_records(self): 123 | return reversed(self.records) 124 | 125 | @property 126 | def reverse_traceback(self): 127 | """Return the same data as traceback, except in reverse order. 128 | """ 129 | 130 | return list(self._get_reformatted_records(self.reverse_records)) 131 | 132 | def _init(self, trcback): 133 | """format a traceback from sys.exc_info() into 7-item tuples, 134 | containing the regular four traceback tuple items, plus the original 135 | template filename, the line number adjusted relative to the template 136 | source, and code line from that line number of the template.""" 137 | 138 | import mako.template 139 | mods = {} 140 | rawrecords = traceback.extract_tb(trcback) 141 | new_trcback = [] 142 | for filename, lineno, function, line in rawrecords: 143 | if not line: 144 | line = '' 145 | try: 146 | (line_map, template_lines) = mods[filename] 147 | except KeyError: 148 | try: 149 | info = mako.template._get_module_info(filename) 150 | module_source = info.code 151 | template_source = info.source 152 | template_filename = info.template_filename or filename 153 | except KeyError: 154 | # A normal .py file (not a Template) 155 | if not compat.py3k: 156 | try: 157 | fp = open(filename, 'rb') 158 | encoding = util.parse_encoding(fp) 159 | fp.close() 160 | except IOError: 161 | encoding = None 162 | if encoding: 163 | line = line.decode(encoding) 164 | else: 165 | line = line.decode('ascii', 'replace') 166 | new_trcback.append((filename, lineno, function, line, 167 | None, None, None, None)) 168 | continue 169 | 170 | template_ln = module_ln = 1 171 | line_map = {} 172 | for line in module_source.split("\n"): 173 | match = re.match(r'\s*# SOURCE LINE (\d+)', line) 174 | if match: 175 | template_ln = int(match.group(1)) 176 | module_ln += 1 177 | line_map[module_ln] = template_ln 178 | template_lines = [line for line in 179 | template_source.split("\n")] 180 | mods[filename] = (line_map, template_lines) 181 | 182 | template_ln = line_map[lineno] 183 | if template_ln <= len(template_lines): 184 | template_line = template_lines[template_ln - 1] 185 | else: 186 | template_line = None 187 | new_trcback.append((filename, lineno, function, 188 | line, template_filename, template_ln, 189 | template_line, template_source)) 190 | if not self.source: 191 | for l in range(len(new_trcback)-1, 0, -1): 192 | if new_trcback[l][5]: 193 | self.source = new_trcback[l][7] 194 | self.lineno = new_trcback[l][5] 195 | break 196 | else: 197 | if new_trcback: 198 | try: 199 | # A normal .py file (not a Template) 200 | fp = open(new_trcback[-1][0], 'rb') 201 | encoding = util.parse_encoding(fp) 202 | fp.seek(0) 203 | self.source = fp.read() 204 | fp.close() 205 | if encoding: 206 | self.source = self.source.decode(encoding) 207 | except IOError: 208 | self.source = '' 209 | self.lineno = new_trcback[-1][1] 210 | return new_trcback 211 | 212 | 213 | def text_error_template(lookup=None): 214 | """Provides a template that renders a stack trace in a similar format to 215 | the Python interpreter, substituting source template filenames, line 216 | numbers and code for that of the originating source template, as 217 | applicable. 218 | 219 | """ 220 | import mako.template 221 | return mako.template.Template(r""" 222 | <%page args="error=None, traceback=None"/> 223 | <%! 224 | from mako.exceptions import RichTraceback 225 | %>\ 226 | <% 227 | tback = RichTraceback(error=error, traceback=traceback) 228 | %>\ 229 | Traceback (most recent call last): 230 | % for (filename, lineno, function, line) in tback.traceback: 231 | File "${filename}", line ${lineno}, in ${function or '?'} 232 | ${line | trim} 233 | % endfor 234 | ${tback.errorname}: ${tback.message} 235 | """) 236 | 237 | 238 | def _install_pygments(): 239 | global syntax_highlight, pygments_html_formatter 240 | from mako.ext.pygmentplugin import syntax_highlight,\ 241 | pygments_html_formatter 242 | 243 | def _install_fallback(): 244 | global syntax_highlight, pygments_html_formatter 245 | from mako.filters import html_escape 246 | pygments_html_formatter = None 247 | def syntax_highlight(filename='', language=None): 248 | return html_escape 249 | 250 | def _install_highlighting(): 251 | try: 252 | _install_pygments() 253 | except ImportError: 254 | _install_fallback() 255 | _install_highlighting() 256 | 257 | def html_error_template(): 258 | """Provides a template that renders a stack trace in an HTML format, 259 | providing an excerpt of code as well as substituting source template 260 | filenames, line numbers and code for that of the originating source 261 | template, as applicable. 262 | 263 | The template's default ``encoding_errors`` value is ``'htmlentityreplace'``. The 264 | template has two options. With the ``full`` option disabled, only a section of 265 | an HTML document is returned. With the ``css`` option disabled, the default 266 | stylesheet won't be included. 267 | 268 | """ 269 | import mako.template 270 | return mako.template.Template(r""" 271 | <%! 272 | from mako.exceptions import RichTraceback, syntax_highlight,\ 273 | pygments_html_formatter 274 | %> 275 | <%page args="full=True, css=True, error=None, traceback=None"/> 276 | % if full: 277 | 278 | 279 | Mako Runtime Error 280 | % endif 281 | % if css: 282 | 311 | % endif 312 | % if full: 313 | 314 | 315 | % endif 316 | 317 |

Error !

318 | <% 319 | tback = RichTraceback(error=error, traceback=traceback) 320 | src = tback.source 321 | line = tback.lineno 322 | if src: 323 | lines = src.split('\n') 324 | else: 325 | lines = None 326 | %> 327 |

${tback.errorname}: ${tback.message|h}

328 | 329 | % if lines: 330 |
331 |
332 | % for index in range(max(0, line-4),min(len(lines), line+5)): 333 | <% 334 | if pygments_html_formatter: 335 | pygments_html_formatter.linenostart = index + 1 336 | %> 337 | % if index + 1 == line: 338 | <% 339 | if pygments_html_formatter: 340 | old_cssclass = pygments_html_formatter.cssclass 341 | pygments_html_formatter.cssclass = 'error ' + old_cssclass 342 | %> 343 | ${lines[index] | syntax_highlight(language='mako')} 344 | <% 345 | if pygments_html_formatter: 346 | pygments_html_formatter.cssclass = old_cssclass 347 | %> 348 | % else: 349 | ${lines[index] | syntax_highlight(language='mako')} 350 | % endif 351 | % endfor 352 |
353 |
354 | % endif 355 | 356 |
357 | % for (filename, lineno, function, line) in tback.reverse_traceback: 358 |
${filename}, line ${lineno}:
359 |
360 | <% 361 | if pygments_html_formatter: 362 | pygments_html_formatter.linenostart = lineno 363 | %> 364 |
${line | syntax_highlight(filename)}
365 |
366 | % endfor 367 |
368 | 369 | % if full: 370 | 371 | 372 | % endif 373 | """, output_encoding=sys.getdefaultencoding(), 374 | encoding_errors='htmlentityreplace') 375 | -------------------------------------------------------------------------------- /gae/mako/ext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacker0x01/Hacker101Coursework/822efa707eaca9094720c26dd183e0a1d8ba63e7/gae/mako/ext/__init__.py -------------------------------------------------------------------------------- /gae/mako/ext/autohandler.py: -------------------------------------------------------------------------------- 1 | # ext/autohandler.py 2 | # Copyright (C) 2006-2012 the Mako authors and contributors 3 | # 4 | # This module is part of Mako and is released under 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 6 | 7 | """adds autohandler functionality to Mako templates. 8 | 9 | requires that the TemplateLookup class is used with templates. 10 | 11 | usage: 12 | 13 | <%! 14 | from mako.ext.autohandler import autohandler 15 | %> 16 | <%inherit file="${autohandler(template, context)}"/> 17 | 18 | 19 | or with custom autohandler filename: 20 | 21 | <%! 22 | from mako.ext.autohandler import autohandler 23 | %> 24 | <%inherit file="${autohandler(template, context, name='somefilename')}"/> 25 | 26 | """ 27 | 28 | import posixpath, os, re 29 | 30 | def autohandler(template, context, name='autohandler'): 31 | lookup = context.lookup 32 | _template_uri = template.module._template_uri 33 | if not lookup.filesystem_checks: 34 | try: 35 | return lookup._uri_cache[(autohandler, _template_uri, name)] 36 | except KeyError: 37 | pass 38 | 39 | tokens = re.findall(r'([^/]+)', posixpath.dirname(_template_uri)) + [name] 40 | while len(tokens): 41 | path = '/' + '/'.join(tokens) 42 | if path != _template_uri and _file_exists(lookup, path): 43 | if not lookup.filesystem_checks: 44 | return lookup._uri_cache.setdefault( 45 | (autohandler, _template_uri, name), path) 46 | else: 47 | return path 48 | if len(tokens) == 1: 49 | break 50 | tokens[-2:] = [name] 51 | 52 | if not lookup.filesystem_checks: 53 | return lookup._uri_cache.setdefault( 54 | (autohandler, _template_uri, name), None) 55 | else: 56 | return None 57 | 58 | def _file_exists(lookup, path): 59 | psub = re.sub(r'^/', '',path) 60 | for d in lookup.directories: 61 | if os.path.exists(d + '/' + psub): 62 | return True 63 | else: 64 | return False 65 | 66 | -------------------------------------------------------------------------------- /gae/mako/ext/babelplugin.py: -------------------------------------------------------------------------------- 1 | # ext/babelplugin.py 2 | # Copyright (C) 2006-2012 the Mako authors and contributors 3 | # 4 | # This module is part of Mako and is released under 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 6 | 7 | """gettext message extraction via Babel: http://babel.edgewall.org/""" 8 | from babel.messages.extract import extract_python 9 | from mako.compat import StringIO 10 | from mako import compat 11 | from mako import lexer, parsetree 12 | 13 | def extract(fileobj, keywords, comment_tags, options): 14 | """Extract messages from Mako templates. 15 | 16 | :param fileobj: the file-like object the messages should be extracted from 17 | :param keywords: a list of keywords (i.e. function names) that should be 18 | recognized as translation functions 19 | :param comment_tags: a list of translator tags to search for and include 20 | in the results 21 | :param options: a dictionary of additional options (optional) 22 | :return: an iterator over ``(lineno, funcname, message, comments)`` tuples 23 | :rtype: ``iterator`` 24 | """ 25 | encoding = options.get('input_encoding', options.get('encoding', None)) 26 | 27 | template_node = lexer.Lexer(fileobj.read(), 28 | input_encoding=encoding).parse() 29 | for extracted in extract_nodes(template_node.get_children(), 30 | keywords, comment_tags, options): 31 | yield extracted 32 | 33 | def extract_nodes(nodes, keywords, comment_tags, options): 34 | """Extract messages from Mako's lexer node objects 35 | 36 | :param nodes: an iterable of Mako parsetree.Node objects to extract from 37 | :param keywords: a list of keywords (i.e. function names) that should be 38 | recognized as translation functions 39 | :param comment_tags: a list of translator tags to search for and include 40 | in the results 41 | :param options: a dictionary of additional options (optional) 42 | :return: an iterator over ``(lineno, funcname, message, comments)`` tuples 43 | :rtype: ``iterator`` 44 | """ 45 | translator_comments = [] 46 | in_translator_comments = False 47 | 48 | for node in nodes: 49 | child_nodes = None 50 | if in_translator_comments and isinstance(node, parsetree.Text) and \ 51 | not node.content.strip(): 52 | # Ignore whitespace within translator comments 53 | continue 54 | 55 | if isinstance(node, parsetree.Comment): 56 | value = node.text.strip() 57 | if in_translator_comments: 58 | translator_comments.extend(_split_comment(node.lineno, value)) 59 | continue 60 | for comment_tag in comment_tags: 61 | if value.startswith(comment_tag): 62 | in_translator_comments = True 63 | translator_comments.extend(_split_comment(node.lineno, 64 | value)) 65 | continue 66 | 67 | if isinstance(node, parsetree.DefTag): 68 | code = node.function_decl.code 69 | child_nodes = node.nodes 70 | elif isinstance(node, parsetree.BlockTag): 71 | code = node.body_decl.code 72 | child_nodes = node.nodes 73 | elif isinstance(node, parsetree.CallTag): 74 | code = node.code.code 75 | child_nodes = node.nodes 76 | elif isinstance(node, parsetree.PageTag): 77 | code = node.body_decl.code 78 | elif isinstance(node, parsetree.CallNamespaceTag): 79 | attribs = ', '.join(['%s=%s' % (key, val) 80 | for key, val in node.attributes.items()]) 81 | code = '{%s}' % attribs 82 | child_nodes = node.nodes 83 | elif isinstance(node, parsetree.ControlLine): 84 | if node.isend: 85 | translator_comments = [] 86 | in_translator_comments = False 87 | continue 88 | code = node.text 89 | elif isinstance(node, parsetree.Code): 90 | # <% and <%! blocks would provide their own translator comments 91 | translator_comments = [] 92 | in_translator_comments = False 93 | 94 | code = node.code.code 95 | elif isinstance(node, parsetree.Expression): 96 | code = node.code.code 97 | else: 98 | translator_comments = [] 99 | in_translator_comments = False 100 | continue 101 | 102 | # Comments don't apply unless they immediately preceed the message 103 | if translator_comments and \ 104 | translator_comments[-1][0] < node.lineno - 1: 105 | translator_comments = [] 106 | else: 107 | translator_comments = \ 108 | [comment[1] for comment in translator_comments] 109 | 110 | if not compat.py3k and isinstance(code, compat.text_type): 111 | code = code.encode('ascii', 'backslashreplace') 112 | code = StringIO(code) 113 | for lineno, funcname, messages, python_translator_comments \ 114 | in extract_python(code, keywords, comment_tags, options): 115 | yield (node.lineno + (lineno - 1), funcname, messages, 116 | translator_comments + python_translator_comments) 117 | 118 | translator_comments = [] 119 | in_translator_comments = False 120 | 121 | if child_nodes: 122 | for extracted in extract_nodes(child_nodes, keywords, comment_tags, 123 | options): 124 | yield extracted 125 | 126 | 127 | def _split_comment(lineno, comment): 128 | """Return the multiline comment at lineno split into a list of comment line 129 | numbers and the accompanying comment line""" 130 | return [(lineno + index, line) for index, line in 131 | enumerate(comment.splitlines())] 132 | -------------------------------------------------------------------------------- /gae/mako/ext/beaker_cache.py: -------------------------------------------------------------------------------- 1 | """Provide a :class:`.CacheImpl` for the Beaker caching system.""" 2 | 3 | from mako import exceptions 4 | 5 | from mako.cache import CacheImpl 6 | 7 | _beaker_cache = None 8 | class BeakerCacheImpl(CacheImpl): 9 | """A :class:`.CacheImpl` provided for the Beaker caching system. 10 | 11 | This plugin is used by default, based on the default 12 | value of ``'beaker'`` for the ``cache_impl`` parameter of the 13 | :class:`.Template` or :class:`.TemplateLookup` classes. 14 | 15 | """ 16 | 17 | def __init__(self, cache): 18 | global _beaker_cache 19 | if _beaker_cache is None: 20 | try: 21 | from beaker import cache as beaker_cache 22 | except ImportError: 23 | raise exceptions.RuntimeException( 24 | "the Beaker package is required to use cache " 25 | "functionality.") 26 | 27 | if 'manager' in cache.template.cache_args: 28 | _beaker_cache = cache.template.cache_args['manager'] 29 | else: 30 | _beaker_cache = beaker_cache.CacheManager() 31 | super(BeakerCacheImpl, self).__init__(cache) 32 | 33 | def _get_cache(self, **kw): 34 | expiretime = kw.pop('timeout', None) 35 | if 'dir' in kw: 36 | kw['data_dir'] = kw.pop('dir') 37 | elif self.cache.template.module_directory: 38 | kw['data_dir'] = self.cache.template.module_directory 39 | 40 | if 'manager' in kw: 41 | kw.pop('manager') 42 | 43 | if kw.get('type') == 'memcached': 44 | kw['type'] = 'ext:memcached' 45 | 46 | if 'region' in kw: 47 | region = kw.pop('region') 48 | cache = _beaker_cache.get_cache_region(self.cache.id, region, **kw) 49 | else: 50 | cache = _beaker_cache.get_cache(self.cache.id, **kw) 51 | cache_args = {'starttime':self.cache.starttime} 52 | if expiretime: 53 | cache_args['expiretime'] = expiretime 54 | return cache, cache_args 55 | 56 | def get_or_create(self, key, creation_function, **kw): 57 | cache, kw = self._get_cache(**kw) 58 | return cache.get(key, createfunc=creation_function, **kw) 59 | 60 | def put(self, key, value, **kw): 61 | cache, kw = self._get_cache(**kw) 62 | cache.put(key, value, **kw) 63 | 64 | def get(self, key, **kw): 65 | cache, kw = self._get_cache(**kw) 66 | return cache.get(key, **kw) 67 | 68 | def invalidate(self, key, **kw): 69 | cache, kw = self._get_cache(**kw) 70 | cache.remove_value(key, **kw) 71 | -------------------------------------------------------------------------------- /gae/mako/ext/preprocessors.py: -------------------------------------------------------------------------------- 1 | # ext/preprocessors.py 2 | # Copyright (C) 2006-2012 the Mako authors and contributors 3 | # 4 | # This module is part of Mako and is released under 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 6 | 7 | """preprocessing functions, used with the 'preprocessor' 8 | argument on Template, TemplateLookup""" 9 | 10 | import re 11 | 12 | def convert_comments(text): 13 | """preprocess old style comments. 14 | 15 | example: 16 | 17 | from mako.ext.preprocessors import convert_comments 18 | t = Template(..., preprocessor=preprocess_comments)""" 19 | return re.sub(r'(?<=\n)\s*#[^#]', "##", text) 20 | 21 | -------------------------------------------------------------------------------- /gae/mako/ext/pygmentplugin.py: -------------------------------------------------------------------------------- 1 | # ext/pygmentplugin.py 2 | # Copyright (C) 2006-2012 the Mako authors and contributors 3 | # 4 | # This module is part of Mako and is released under 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 6 | 7 | from pygments.lexers.web import \ 8 | HtmlLexer, XmlLexer, JavascriptLexer, CssLexer 9 | from pygments.lexers.agile import PythonLexer, Python3Lexer 10 | from pygments.lexer import DelegatingLexer, RegexLexer, bygroups, \ 11 | include, using 12 | from pygments.token import \ 13 | Text, Comment, Operator, Keyword, Name, String, Other 14 | from pygments.formatters.html import HtmlFormatter 15 | from pygments import highlight 16 | from mako import compat 17 | 18 | class MakoLexer(RegexLexer): 19 | name = 'Mako' 20 | aliases = ['mako'] 21 | filenames = ['*.mao'] 22 | 23 | tokens = { 24 | 'root': [ 25 | (r'(\s*)(\%)(\s*end(?:\w+))(\n|\Z)', 26 | bygroups(Text, Comment.Preproc, Keyword, Other)), 27 | (r'(\s*)(\%(?!%))([^\n]*)(\n|\Z)', 28 | bygroups(Text, Comment.Preproc, using(PythonLexer), Other)), 29 | (r'(\s*)(##[^\n]*)(\n|\Z)', 30 | bygroups(Text, Comment.Preproc, Other)), 31 | (r'''(?s)<%doc>.*?''', Comment.Preproc), 32 | (r'(<%)([\w\.\:]+)', 33 | bygroups(Comment.Preproc, Name.Builtin), 'tag'), 34 | (r'()', 35 | bygroups(Comment.Preproc, Name.Builtin, Comment.Preproc)), 36 | (r'<%(?=([\w\.\:]+))', Comment.Preproc, 'ondeftags'), 37 | (r'(<%(?:!?))(.*?)(%>)(?s)', 38 | bygroups(Comment.Preproc, using(PythonLexer), Comment.Preproc)), 39 | (r'(\$\{)(.*?)(\})', 40 | bygroups(Comment.Preproc, using(PythonLexer), Comment.Preproc)), 41 | (r'''(?sx) 42 | (.+?) # anything, followed by: 43 | (?: 44 | (?<=\n)(?=%(?!%)|\#\#) | # an eval or comment line 45 | (?=\#\*) | # multiline comment 46 | (?=', Comment.Preproc, '#pop'), 66 | (r'\s+', Text), 67 | ], 68 | 'attr': [ 69 | ('".*?"', String, '#pop'), 70 | ("'.*?'", String, '#pop'), 71 | (r'[^\s>]+', String, '#pop'), 72 | ], 73 | } 74 | 75 | 76 | class MakoHtmlLexer(DelegatingLexer): 77 | name = 'HTML+Mako' 78 | aliases = ['html+mako'] 79 | 80 | def __init__(self, **options): 81 | super(MakoHtmlLexer, self).__init__(HtmlLexer, MakoLexer, 82 | **options) 83 | 84 | class MakoXmlLexer(DelegatingLexer): 85 | name = 'XML+Mako' 86 | aliases = ['xml+mako'] 87 | 88 | def __init__(self, **options): 89 | super(MakoXmlLexer, self).__init__(XmlLexer, MakoLexer, 90 | **options) 91 | 92 | class MakoJavascriptLexer(DelegatingLexer): 93 | name = 'JavaScript+Mako' 94 | aliases = ['js+mako', 'javascript+mako'] 95 | 96 | def __init__(self, **options): 97 | super(MakoJavascriptLexer, self).__init__(JavascriptLexer, 98 | MakoLexer, **options) 99 | 100 | class MakoCssLexer(DelegatingLexer): 101 | name = 'CSS+Mako' 102 | aliases = ['css+mako'] 103 | 104 | def __init__(self, **options): 105 | super(MakoCssLexer, self).__init__(CssLexer, MakoLexer, 106 | **options) 107 | 108 | 109 | pygments_html_formatter = HtmlFormatter(cssclass='syntax-highlighted', 110 | linenos=True) 111 | def syntax_highlight(filename='', language=None): 112 | mako_lexer = MakoLexer() 113 | if compat.py3k: 114 | python_lexer = Python3Lexer() 115 | else: 116 | python_lexer = PythonLexer() 117 | if filename.startswith('memory:') or language == 'mako': 118 | return lambda string: highlight(string, mako_lexer, 119 | pygments_html_formatter) 120 | return lambda string: highlight(string, python_lexer, 121 | pygments_html_formatter) 122 | 123 | -------------------------------------------------------------------------------- /gae/mako/ext/turbogears.py: -------------------------------------------------------------------------------- 1 | # ext/turbogears.py 2 | # Copyright (C) 2006-2012 the Mako authors and contributors 3 | # 4 | # This module is part of Mako and is released under 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 6 | 7 | import re, inspect 8 | from mako.lookup import TemplateLookup 9 | from mako.template import Template 10 | 11 | class TGPlugin(object): 12 | """TurboGears compatible Template Plugin.""" 13 | 14 | def __init__(self, extra_vars_func=None, options=None, extension='mak'): 15 | self.extra_vars_func = extra_vars_func 16 | self.extension = extension 17 | if not options: 18 | options = {} 19 | 20 | # Pull the options out and initialize the lookup 21 | lookup_options = {} 22 | for k, v in options.items(): 23 | if k.startswith('mako.'): 24 | lookup_options[k[5:]] = v 25 | elif k in ['directories', 'filesystem_checks', 'module_directory']: 26 | lookup_options[k] = v 27 | self.lookup = TemplateLookup(**lookup_options) 28 | 29 | self.tmpl_options = {} 30 | # transfer lookup args to template args, based on those available 31 | # in getargspec 32 | for kw in inspect.getargspec(Template.__init__)[0]: 33 | if kw in lookup_options: 34 | self.tmpl_options[kw] = lookup_options[kw] 35 | 36 | def load_template(self, templatename, template_string=None): 37 | """Loads a template from a file or a string""" 38 | if template_string is not None: 39 | return Template(template_string, **self.tmpl_options) 40 | # Translate TG dot notation to normal / template path 41 | if '/' not in templatename: 42 | templatename = '/' + templatename.replace('.', '/') + '.' +\ 43 | self.extension 44 | 45 | # Lookup template 46 | return self.lookup.get_template(templatename) 47 | 48 | def render(self, info, format="html", fragment=False, template=None): 49 | if isinstance(template, str): 50 | template = self.load_template(template) 51 | 52 | # Load extra vars func if provided 53 | if self.extra_vars_func: 54 | info.update(self.extra_vars_func()) 55 | 56 | return template.render(**info) 57 | 58 | -------------------------------------------------------------------------------- /gae/mako/filters.py: -------------------------------------------------------------------------------- 1 | # mako/filters.py 2 | # Copyright (C) 2006-2012 the Mako authors and contributors 3 | # 4 | # This module is part of Mako and is released under 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 6 | 7 | 8 | import re 9 | import codecs 10 | 11 | from mako.compat import quote_plus, unquote_plus, codepoint2name, \ 12 | name2codepoint 13 | 14 | from mako import compat 15 | 16 | xml_escapes = { 17 | '&': '&', 18 | '>': '>', 19 | '<': '<', 20 | '"': '"', # also " in html-only 21 | "'": ''' # also ' in html-only 22 | } 23 | 24 | # XXX: " is valid in HTML and XML 25 | # ' is not valid HTML, but is valid XML 26 | 27 | def legacy_html_escape(s): 28 | """legacy HTML escape for non-unicode mode.""" 29 | s = s.replace("&", "&") 30 | s = s.replace(">", ">") 31 | s = s.replace("<", "<") 32 | s = s.replace('"', """) 33 | s = s.replace("'", "'") 34 | return s 35 | 36 | 37 | try: 38 | import markupsafe 39 | html_escape = markupsafe.escape 40 | except ImportError: 41 | html_escape = legacy_html_escape 42 | 43 | def xml_escape(string): 44 | return re.sub(r'([&<"\'>])', lambda m: xml_escapes[m.group()], string) 45 | 46 | def url_escape(string): 47 | # convert into a list of octets 48 | string = string.encode("utf8") 49 | return quote_plus(string) 50 | 51 | def url_unescape(string): 52 | text = unquote_plus(string) 53 | if not is_ascii_str(text): 54 | text = text.decode("utf8") 55 | return text 56 | 57 | def trim(string): 58 | return string.strip() 59 | 60 | 61 | class Decode(object): 62 | def __getattr__(self, key): 63 | def decode(x): 64 | if isinstance(x, compat.text_type): 65 | return x 66 | elif not isinstance(x, compat.binary_type): 67 | return compat.text_type(str(x), encoding=key) 68 | else: 69 | return compat.text_type(x, encoding=key) 70 | return decode 71 | decode = Decode() 72 | 73 | 74 | _ASCII_re = re.compile(r'\A[\x00-\x7f]*\Z') 75 | 76 | def is_ascii_str(text): 77 | return isinstance(text, str) and _ASCII_re.match(text) 78 | 79 | ################################################################ 80 | 81 | class XMLEntityEscaper(object): 82 | def __init__(self, codepoint2name, name2codepoint): 83 | self.codepoint2entity = dict([(c, compat.text_type('&%s;' % n)) 84 | for c, n in codepoint2name.items()]) 85 | self.name2codepoint = name2codepoint 86 | 87 | def escape_entities(self, text): 88 | """Replace characters with their character entity references. 89 | 90 | Only characters corresponding to a named entity are replaced. 91 | """ 92 | return compat.text_type(text).translate(self.codepoint2entity) 93 | 94 | def __escape(self, m): 95 | codepoint = ord(m.group()) 96 | try: 97 | return self.codepoint2entity[codepoint] 98 | except (KeyError, IndexError): 99 | return '&#x%X;' % codepoint 100 | 101 | 102 | __escapable = re.compile(r'["&<>]|[^\x00-\x7f]') 103 | 104 | def escape(self, text): 105 | """Replace characters with their character references. 106 | 107 | Replace characters by their named entity references. 108 | Non-ASCII characters, if they do not have a named entity reference, 109 | are replaced by numerical character references. 110 | 111 | The return value is guaranteed to be ASCII. 112 | """ 113 | return self.__escapable.sub(self.__escape, compat.text_type(text) 114 | ).encode('ascii') 115 | 116 | # XXX: This regexp will not match all valid XML entity names__. 117 | # (It punts on details involving involving CombiningChars and Extenders.) 118 | # 119 | # .. __: http://www.w3.org/TR/2000/REC-xml-20001006#NT-EntityRef 120 | __characterrefs = re.compile(r'''& (?: 121 | \#(\d+) 122 | | \#x([\da-f]+) 123 | | ( (?!\d) [:\w] [-.:\w]+ ) 124 | ) ;''', 125 | re.X | re.UNICODE) 126 | 127 | def __unescape(self, m): 128 | dval, hval, name = m.groups() 129 | if dval: 130 | codepoint = int(dval) 131 | elif hval: 132 | codepoint = int(hval, 16) 133 | else: 134 | codepoint = self.name2codepoint.get(name, 0xfffd) 135 | # U+FFFD = "REPLACEMENT CHARACTER" 136 | if codepoint < 128: 137 | return chr(codepoint) 138 | return chr(codepoint) 139 | 140 | def unescape(self, text): 141 | """Unescape character references. 142 | 143 | All character references (both entity references and numerical 144 | character references) are unescaped. 145 | """ 146 | return self.__characterrefs.sub(self.__unescape, text) 147 | 148 | 149 | _html_entities_escaper = XMLEntityEscaper(codepoint2name, name2codepoint) 150 | 151 | html_entities_escape = _html_entities_escaper.escape_entities 152 | html_entities_unescape = _html_entities_escaper.unescape 153 | 154 | 155 | def htmlentityreplace_errors(ex): 156 | """An encoding error handler. 157 | 158 | This python `codecs`_ error handler replaces unencodable 159 | characters with HTML entities, or, if no HTML entity exists for 160 | the character, XML character references. 161 | 162 | >>> u'The cost was \u20ac12.'.encode('latin1', 'htmlentityreplace') 163 | 'The cost was €12.' 164 | """ 165 | if isinstance(ex, UnicodeEncodeError): 166 | # Handle encoding errors 167 | bad_text = ex.object[ex.start:ex.end] 168 | text = _html_entities_escaper.escape(bad_text) 169 | return (compat.text_type(text), ex.end) 170 | raise ex 171 | 172 | codecs.register_error('htmlentityreplace', htmlentityreplace_errors) 173 | 174 | 175 | # TODO: options to make this dynamic per-compilation will be added in a later 176 | # release 177 | DEFAULT_ESCAPES = { 178 | 'x': 'filters.xml_escape', 179 | 'h': 'filters.html_escape', 180 | 'u': 'filters.url_escape', 181 | 'trim': 'filters.trim', 182 | 'entity': 'filters.html_entities_escape', 183 | 'unicode': 'unicode', 184 | 'decode': 'decode', 185 | 'str': 'str', 186 | 'n': 'n' 187 | } 188 | 189 | if compat.py3k: 190 | DEFAULT_ESCAPES.update({ 191 | 'unicode': 'str' 192 | }) 193 | 194 | NON_UNICODE_ESCAPES = DEFAULT_ESCAPES.copy() 195 | NON_UNICODE_ESCAPES['h'] = 'filters.legacy_html_escape' 196 | 197 | -------------------------------------------------------------------------------- /gae/mako/pygen.py: -------------------------------------------------------------------------------- 1 | # mako/pygen.py 2 | # Copyright (C) 2006-2012 the Mako authors and contributors 3 | # 4 | # This module is part of Mako and is released under 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 6 | 7 | """utilities for generating and formatting literal Python code.""" 8 | 9 | import re 10 | from mako import exceptions 11 | 12 | class PythonPrinter(object): 13 | def __init__(self, stream): 14 | # indentation counter 15 | self.indent = 0 16 | 17 | # a stack storing information about why we incremented 18 | # the indentation counter, to help us determine if we 19 | # should decrement it 20 | self.indent_detail = [] 21 | 22 | # the string of whitespace multiplied by the indent 23 | # counter to produce a line 24 | self.indentstring = " " 25 | 26 | # the stream we are writing to 27 | self.stream = stream 28 | 29 | # a list of lines that represents a buffered "block" of code, 30 | # which can be later printed relative to an indent level 31 | self.line_buffer = [] 32 | 33 | self.in_indent_lines = False 34 | 35 | self._reset_multi_line_flags() 36 | 37 | def write(self, text): 38 | self.stream.write(text) 39 | 40 | def write_indented_block(self, block): 41 | """print a line or lines of python which already contain indentation. 42 | 43 | The indentation of the total block of lines will be adjusted to that of 44 | the current indent level.""" 45 | self.in_indent_lines = False 46 | for l in re.split(r'\r?\n', block): 47 | self.line_buffer.append(l) 48 | 49 | def writelines(self, *lines): 50 | """print a series of lines of python.""" 51 | for line in lines: 52 | self.writeline(line) 53 | 54 | def writeline(self, line): 55 | """print a line of python, indenting it according to the current 56 | indent level. 57 | 58 | this also adjusts the indentation counter according to the 59 | content of the line. 60 | 61 | """ 62 | 63 | if not self.in_indent_lines: 64 | self._flush_adjusted_lines() 65 | self.in_indent_lines = True 66 | 67 | if (line is None or 68 | re.match(r"^\s*#",line) or 69 | re.match(r"^\s*$", line) 70 | ): 71 | hastext = False 72 | else: 73 | hastext = True 74 | 75 | is_comment = line and len(line) and line[0] == '#' 76 | 77 | # see if this line should decrease the indentation level 78 | if (not is_comment and 79 | (not hastext or self._is_unindentor(line)) 80 | ): 81 | 82 | if self.indent > 0: 83 | self.indent -=1 84 | # if the indent_detail stack is empty, the user 85 | # probably put extra closures - the resulting 86 | # module wont compile. 87 | if len(self.indent_detail) == 0: 88 | raise exceptions.SyntaxException( 89 | "Too many whitespace closures") 90 | self.indent_detail.pop() 91 | 92 | if line is None: 93 | return 94 | 95 | # write the line 96 | self.stream.write(self._indent_line(line) + "\n") 97 | 98 | # see if this line should increase the indentation level. 99 | # note that a line can both decrase (before printing) and 100 | # then increase (after printing) the indentation level. 101 | 102 | if re.search(r":[ \t]*(?:#.*)?$", line): 103 | # increment indentation count, and also 104 | # keep track of what the keyword was that indented us, 105 | # if it is a python compound statement keyword 106 | # where we might have to look for an "unindent" keyword 107 | match = re.match(r"^\s*(if|try|elif|while|for|with)", line) 108 | if match: 109 | # its a "compound" keyword, so we will check for "unindentors" 110 | indentor = match.group(1) 111 | self.indent +=1 112 | self.indent_detail.append(indentor) 113 | else: 114 | indentor = None 115 | # its not a "compound" keyword. but lets also 116 | # test for valid Python keywords that might be indenting us, 117 | # else assume its a non-indenting line 118 | m2 = re.match(r"^\s*(def|class|else|elif|except|finally)", 119 | line) 120 | if m2: 121 | self.indent += 1 122 | self.indent_detail.append(indentor) 123 | 124 | def close(self): 125 | """close this printer, flushing any remaining lines.""" 126 | self._flush_adjusted_lines() 127 | 128 | def _is_unindentor(self, line): 129 | """return true if the given line is an 'unindentor', 130 | relative to the last 'indent' event received. 131 | 132 | """ 133 | 134 | # no indentation detail has been pushed on; return False 135 | if len(self.indent_detail) == 0: 136 | return False 137 | 138 | indentor = self.indent_detail[-1] 139 | 140 | # the last indent keyword we grabbed is not a 141 | # compound statement keyword; return False 142 | if indentor is None: 143 | return False 144 | 145 | # if the current line doesnt have one of the "unindentor" keywords, 146 | # return False 147 | match = re.match(r"^\s*(else|elif|except|finally).*\:", line) 148 | if not match: 149 | return False 150 | 151 | # whitespace matches up, we have a compound indentor, 152 | # and this line has an unindentor, this 153 | # is probably good enough 154 | return True 155 | 156 | # should we decide that its not good enough, heres 157 | # more stuff to check. 158 | #keyword = match.group(1) 159 | 160 | # match the original indent keyword 161 | #for crit in [ 162 | # (r'if|elif', r'else|elif'), 163 | # (r'try', r'except|finally|else'), 164 | # (r'while|for', r'else'), 165 | #]: 166 | # if re.match(crit[0], indentor) and re.match(crit[1], keyword): 167 | # return True 168 | 169 | #return False 170 | 171 | def _indent_line(self, line, stripspace=''): 172 | """indent the given line according to the current indent level. 173 | 174 | stripspace is a string of space that will be truncated from the 175 | start of the line before indenting.""" 176 | 177 | return re.sub(r"^%s" % stripspace, self.indentstring 178 | * self.indent, line) 179 | 180 | def _reset_multi_line_flags(self): 181 | """reset the flags which would indicate we are in a backslashed 182 | or triple-quoted section.""" 183 | 184 | self.backslashed, self.triplequoted = False, False 185 | 186 | def _in_multi_line(self, line): 187 | """return true if the given line is part of a multi-line block, 188 | via backslash or triple-quote.""" 189 | 190 | # we are only looking for explicitly joined lines here, not 191 | # implicit ones (i.e. brackets, braces etc.). this is just to 192 | # guard against the possibility of modifying the space inside of 193 | # a literal multiline string with unfortunately placed 194 | # whitespace 195 | 196 | current_state = (self.backslashed or self.triplequoted) 197 | 198 | if re.search(r"\\$", line): 199 | self.backslashed = True 200 | else: 201 | self.backslashed = False 202 | 203 | triples = len(re.findall(r"\"\"\"|\'\'\'", line)) 204 | if triples == 1 or triples % 2 != 0: 205 | self.triplequoted = not self.triplequoted 206 | 207 | return current_state 208 | 209 | def _flush_adjusted_lines(self): 210 | stripspace = None 211 | self._reset_multi_line_flags() 212 | 213 | for entry in self.line_buffer: 214 | if self._in_multi_line(entry): 215 | self.stream.write(entry + "\n") 216 | else: 217 | entry = entry.expandtabs() 218 | if stripspace is None and re.search(r"^[ \t]*[^# \t]", entry): 219 | stripspace = re.match(r"^([ \t]*)", entry).group(1) 220 | self.stream.write(self._indent_line(entry, stripspace) + "\n") 221 | 222 | self.line_buffer = [] 223 | self._reset_multi_line_flags() 224 | 225 | 226 | def adjust_whitespace(text): 227 | """remove the left-whitespace margin of a block of Python code.""" 228 | 229 | state = [False, False] 230 | (backslashed, triplequoted) = (0, 1) 231 | 232 | def in_multi_line(line): 233 | start_state = (state[backslashed] or state[triplequoted]) 234 | 235 | if re.search(r"\\$", line): 236 | state[backslashed] = True 237 | else: 238 | state[backslashed] = False 239 | 240 | def match(reg, t): 241 | m = re.match(reg, t) 242 | if m: 243 | return m, t[len(m.group(0)):] 244 | else: 245 | return None, t 246 | 247 | while line: 248 | if state[triplequoted]: 249 | m, line = match(r"%s" % state[triplequoted], line) 250 | if m: 251 | state[triplequoted] = False 252 | else: 253 | m, line = match(r".*?(?=%s|$)" % state[triplequoted], line) 254 | else: 255 | m, line = match(r'#', line) 256 | if m: 257 | return start_state 258 | 259 | m, line = match(r"\"\"\"|\'\'\'", line) 260 | if m: 261 | state[triplequoted] = m.group(0) 262 | continue 263 | 264 | m, line = match(r".*?(?=\"\"\"|\'\'\'|#|$)", line) 265 | 266 | return start_state 267 | 268 | def _indent_line(line, stripspace = ''): 269 | return re.sub(r"^%s" % stripspace, '', line) 270 | 271 | lines = [] 272 | stripspace = None 273 | 274 | for line in re.split(r'\r?\n', text): 275 | if in_multi_line(line): 276 | lines.append(line) 277 | else: 278 | line = line.expandtabs() 279 | if stripspace is None and re.search(r"^[ \t]*[^# \t]", line): 280 | stripspace = re.match(r"^([ \t]*)", line).group(1) 281 | lines.append(_indent_line(line, stripspace)) 282 | return "\n".join(lines) 283 | -------------------------------------------------------------------------------- /gae/mako/util.py: -------------------------------------------------------------------------------- 1 | # mako/util.py 2 | # Copyright (C) 2006-2012 the Mako authors and contributors 3 | # 4 | # This module is part of Mako and is released under 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 6 | 7 | import re 8 | import collections 9 | import codecs 10 | import os 11 | from mako import compat 12 | import operator 13 | 14 | def function_named(fn, name): 15 | """Return a function with a given __name__. 16 | 17 | Will assign to __name__ and return the original function if possible on 18 | the Python implementation, otherwise a new function will be constructed. 19 | 20 | """ 21 | fn.__name__ = name 22 | return fn 23 | 24 | 25 | class PluginLoader(object): 26 | def __init__(self, group): 27 | self.group = group 28 | self.impls = {} 29 | 30 | def load(self, name): 31 | if name in self.impls: 32 | return self.impls[name]() 33 | else: 34 | import pkg_resources 35 | for impl in pkg_resources.iter_entry_points( 36 | self.group, 37 | name): 38 | self.impls[name] = impl.load 39 | return impl.load() 40 | else: 41 | from mako import exceptions 42 | raise exceptions.RuntimeException( 43 | "Can't load plugin %s %s" % 44 | (self.group, name)) 45 | 46 | def register(self, name, modulepath, objname): 47 | def load(): 48 | mod = __import__(modulepath) 49 | for token in modulepath.split(".")[1:]: 50 | mod = getattr(mod, token) 51 | return getattr(mod, objname) 52 | self.impls[name] = load 53 | 54 | def verify_directory(dir): 55 | """create and/or verify a filesystem directory.""" 56 | 57 | tries = 0 58 | 59 | while not os.path.exists(dir): 60 | try: 61 | tries += 1 62 | os.makedirs(dir, compat.octal("0775")) 63 | except: 64 | if tries > 5: 65 | raise 66 | 67 | def to_list(x, default=None): 68 | if x is None: 69 | return default 70 | if not isinstance(x, (list, tuple)): 71 | return [x] 72 | else: 73 | return x 74 | 75 | 76 | class memoized_property(object): 77 | """A read-only @property that is only evaluated once.""" 78 | def __init__(self, fget, doc=None): 79 | self.fget = fget 80 | self.__doc__ = doc or fget.__doc__ 81 | self.__name__ = fget.__name__ 82 | 83 | def __get__(self, obj, cls): 84 | if obj is None: 85 | return self 86 | obj.__dict__[self.__name__] = result = self.fget(obj) 87 | return result 88 | 89 | class memoized_instancemethod(object): 90 | """Decorate a method memoize its return value. 91 | 92 | Best applied to no-arg methods: memoization is not sensitive to 93 | argument values, and will always return the same value even when 94 | called with different arguments. 95 | 96 | """ 97 | def __init__(self, fget, doc=None): 98 | self.fget = fget 99 | self.__doc__ = doc or fget.__doc__ 100 | self.__name__ = fget.__name__ 101 | 102 | def __get__(self, obj, cls): 103 | if obj is None: 104 | return self 105 | def oneshot(*args, **kw): 106 | result = self.fget(obj, *args, **kw) 107 | memo = lambda *a, **kw: result 108 | memo.__name__ = self.__name__ 109 | memo.__doc__ = self.__doc__ 110 | obj.__dict__[self.__name__] = memo 111 | return result 112 | oneshot.__name__ = self.__name__ 113 | oneshot.__doc__ = self.__doc__ 114 | return oneshot 115 | 116 | class SetLikeDict(dict): 117 | """a dictionary that has some setlike methods on it""" 118 | def union(self, other): 119 | """produce a 'union' of this dict and another (at the key level). 120 | 121 | values in the second dict take precedence over that of the first""" 122 | x = SetLikeDict(**self) 123 | x.update(other) 124 | return x 125 | 126 | class FastEncodingBuffer(object): 127 | """a very rudimentary buffer that is faster than StringIO, 128 | but doesn't crash on unicode data like cStringIO.""" 129 | 130 | def __init__(self, encoding=None, errors='strict', as_unicode=False): 131 | self.data = collections.deque() 132 | self.encoding = encoding 133 | if as_unicode: 134 | self.delim = compat.u('') 135 | else: 136 | self.delim = '' 137 | self.as_unicode = as_unicode 138 | self.errors = errors 139 | self.write = self.data.append 140 | 141 | def truncate(self): 142 | self.data = collections.deque() 143 | self.write = self.data.append 144 | 145 | def getvalue(self): 146 | if self.encoding: 147 | return self.delim.join(self.data).encode(self.encoding, 148 | self.errors) 149 | else: 150 | return self.delim.join(self.data) 151 | 152 | class LRUCache(dict): 153 | """A dictionary-like object that stores a limited number of items, 154 | discarding lesser used items periodically. 155 | 156 | this is a rewrite of LRUCache from Myghty to use a periodic timestamp-based 157 | paradigm so that synchronization is not really needed. the size management 158 | is inexact. 159 | """ 160 | 161 | class _Item(object): 162 | def __init__(self, key, value): 163 | self.key = key 164 | self.value = value 165 | self.timestamp = compat.time_func() 166 | def __repr__(self): 167 | return repr(self.value) 168 | 169 | def __init__(self, capacity, threshold=.5): 170 | self.capacity = capacity 171 | self.threshold = threshold 172 | 173 | def __getitem__(self, key): 174 | item = dict.__getitem__(self, key) 175 | item.timestamp = compat.time_func() 176 | return item.value 177 | 178 | def values(self): 179 | return [i.value for i in dict.values(self)] 180 | 181 | def setdefault(self, key, value): 182 | if key in self: 183 | return self[key] 184 | else: 185 | self[key] = value 186 | return value 187 | 188 | def __setitem__(self, key, value): 189 | item = dict.get(self, key) 190 | if item is None: 191 | item = self._Item(key, value) 192 | dict.__setitem__(self, key, item) 193 | else: 194 | item.value = value 195 | self._manage_size() 196 | 197 | def _manage_size(self): 198 | while len(self) > self.capacity + self.capacity * self.threshold: 199 | bytime = sorted(dict.values(self), 200 | key=operator.attrgetter('timestamp'), reverse=True) 201 | for item in bytime[self.capacity:]: 202 | try: 203 | del self[item.key] 204 | except KeyError: 205 | # if we couldn't find a key, most likely some other thread 206 | # broke in on us. loop around and try again 207 | break 208 | 209 | # Regexp to match python magic encoding line 210 | _PYTHON_MAGIC_COMMENT_re = re.compile( 211 | r'[ \t\f]* \# .* coding[=:][ \t]*([-\w.]+)', 212 | re.VERBOSE) 213 | 214 | def parse_encoding(fp): 215 | """Deduce the encoding of a Python source file (binary mode) from magic 216 | comment. 217 | 218 | It does this in the same way as the `Python interpreter`__ 219 | 220 | .. __: http://docs.python.org/ref/encodings.html 221 | 222 | The ``fp`` argument should be a seekable file object in binary mode. 223 | """ 224 | pos = fp.tell() 225 | fp.seek(0) 226 | try: 227 | line1 = fp.readline() 228 | has_bom = line1.startswith(codecs.BOM_UTF8) 229 | if has_bom: 230 | line1 = line1[len(codecs.BOM_UTF8):] 231 | 232 | m = _PYTHON_MAGIC_COMMENT_re.match(line1.decode('ascii', 'ignore')) 233 | if not m: 234 | try: 235 | import parser 236 | parser.suite(line1.decode('ascii', 'ignore')) 237 | except (ImportError, SyntaxError): 238 | # Either it's a real syntax error, in which case the source 239 | # is not valid python source, or line2 is a continuation of 240 | # line1, in which case we don't want to scan line2 for a magic 241 | # comment. 242 | pass 243 | else: 244 | line2 = fp.readline() 245 | m = _PYTHON_MAGIC_COMMENT_re.match( 246 | line2.decode('ascii', 'ignore')) 247 | 248 | if has_bom: 249 | if m: 250 | raise SyntaxError("python refuses to compile code with both a UTF8" \ 251 | " byte-order-mark and a magic encoding comment") 252 | return 'utf_8' 253 | elif m: 254 | return m.group(1) 255 | else: 256 | return None 257 | finally: 258 | fp.seek(pos) 259 | 260 | def sorted_dict_repr(d): 261 | """repr() a dictionary with the keys in order. 262 | 263 | Used by the lexer unit test to compare parse trees based on strings. 264 | 265 | """ 266 | keys = list(d.keys()) 267 | keys.sort() 268 | return "{" + ", ".join(["%r: %r" % (k, d[k]) for k in keys]) + "}" 269 | 270 | def restore__ast(_ast): 271 | """Attempt to restore the required classes to the _ast module if it 272 | appears to be missing them 273 | """ 274 | if hasattr(_ast, 'AST'): 275 | return 276 | _ast.PyCF_ONLY_AST = 2 << 9 277 | m = compile("""\ 278 | def foo(): pass 279 | class Bar(object): pass 280 | if False: pass 281 | baz = 'mako' 282 | 1 + 2 - 3 * 4 / 5 283 | 6 // 7 % 8 << 9 >> 10 284 | 11 & 12 ^ 13 | 14 285 | 15 and 16 or 17 286 | -baz + (not +18) - ~17 287 | baz and 'foo' or 'bar' 288 | (mako is baz == baz) is not baz != mako 289 | mako > baz < mako >= baz <= mako 290 | mako in baz not in mako""", '', 'exec', _ast.PyCF_ONLY_AST) 291 | _ast.Module = type(m) 292 | 293 | for cls in _ast.Module.__mro__: 294 | if cls.__name__ == 'mod': 295 | _ast.mod = cls 296 | elif cls.__name__ == 'AST': 297 | _ast.AST = cls 298 | 299 | _ast.FunctionDef = type(m.body[0]) 300 | _ast.ClassDef = type(m.body[1]) 301 | _ast.If = type(m.body[2]) 302 | 303 | _ast.Name = type(m.body[3].targets[0]) 304 | _ast.Store = type(m.body[3].targets[0].ctx) 305 | _ast.Str = type(m.body[3].value) 306 | 307 | _ast.Sub = type(m.body[4].value.op) 308 | _ast.Add = type(m.body[4].value.left.op) 309 | _ast.Div = type(m.body[4].value.right.op) 310 | _ast.Mult = type(m.body[4].value.right.left.op) 311 | 312 | _ast.RShift = type(m.body[5].value.op) 313 | _ast.LShift = type(m.body[5].value.left.op) 314 | _ast.Mod = type(m.body[5].value.left.left.op) 315 | _ast.FloorDiv = type(m.body[5].value.left.left.left.op) 316 | 317 | _ast.BitOr = type(m.body[6].value.op) 318 | _ast.BitXor = type(m.body[6].value.left.op) 319 | _ast.BitAnd = type(m.body[6].value.left.left.op) 320 | 321 | _ast.Or = type(m.body[7].value.op) 322 | _ast.And = type(m.body[7].value.values[0].op) 323 | 324 | _ast.Invert = type(m.body[8].value.right.op) 325 | _ast.Not = type(m.body[8].value.left.right.op) 326 | _ast.UAdd = type(m.body[8].value.left.right.operand.op) 327 | _ast.USub = type(m.body[8].value.left.left.op) 328 | 329 | _ast.Or = type(m.body[9].value.op) 330 | _ast.And = type(m.body[9].value.values[0].op) 331 | 332 | _ast.IsNot = type(m.body[10].value.ops[0]) 333 | _ast.NotEq = type(m.body[10].value.ops[1]) 334 | _ast.Is = type(m.body[10].value.left.ops[0]) 335 | _ast.Eq = type(m.body[10].value.left.ops[1]) 336 | 337 | _ast.Gt = type(m.body[11].value.ops[0]) 338 | _ast.Lt = type(m.body[11].value.ops[1]) 339 | _ast.GtE = type(m.body[11].value.ops[2]) 340 | _ast.LtE = type(m.body[11].value.ops[3]) 341 | 342 | _ast.In = type(m.body[12].value.ops[0]) 343 | _ast.NotIn = type(m.body[12].value.ops[1]) 344 | 345 | 346 | 347 | def read_file(path, mode='rb'): 348 | fp = open(path, mode) 349 | try: 350 | data = fp.read() 351 | return data 352 | finally: 353 | fp.close() 354 | 355 | def read_python_file(path): 356 | fp = open(path, "rb") 357 | try: 358 | encoding = parse_encoding(fp) 359 | data = fp.read() 360 | if encoding: 361 | data = data.decode(encoding) 362 | return data 363 | finally: 364 | fp.close() 365 | 366 | -------------------------------------------------------------------------------- /gae/static/exploit.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /gae/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacker0x01/Hacker101Coursework/822efa707eaca9094720c26dd183e0a1d8ba63e7/gae/static/favicon.png -------------------------------------------------------------------------------- /gae/static/img/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacker0x01/Hacker101Coursework/822efa707eaca9094720c26dd183e0a1d8ba63e7/gae/static/img/down.png -------------------------------------------------------------------------------- /gae/static/img/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacker0x01/Hacker101Coursework/822efa707eaca9094720c26dd183e0a1d8ba63e7/gae/static/img/up.png -------------------------------------------------------------------------------- /gae/static/report0.txt: -------------------------------------------------------------------------------- 1 | Here's a simple bug report for level 0. There are quite a few other bugs to find, so don't think you can get away with just this one! 2 | 3 | Title: Cross-Site Request Forgery 4 | 5 | Severity: Critical 6 | 7 | Description: The "Transfer Funds" functionality is vulnerable to CSRF due to no session-specific random token being attached to the form. 8 | 9 | Reproduction Steps: 10 | 1) Go to the Transfer Funds page 11 | 2) Submit a funds transfer 12 | 3) Note that the only data transmitted is the destination and the amount. 13 | 14 | You can also use the following proof of concept to submit an automatic transfer: 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | Impact: Due to the simple nature of this vulnerability, it's possible for an attacker to transfer funds from any victim whom he can convince to access a page controlled by the attacker. In this proof of concept, it's done via form autosubmission in plain view, but this could be performed in a hidden IFrame, leaving the user no clue that an attack has happened at all. 23 | 24 | Mitigation: Proper CSRF tokens should be used on all forms. You can read more here: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) 25 | 26 | Affected Assets: http://h101levels.appspot.com/levels/0 27 | -------------------------------------------------------------------------------- /gae/static/report01.html: -------------------------------------------------------------------------------- 1 |

Level0

2 | 3 |

Cross-Site Request Forgery

4 | 5 |

Severity: Critical

6 | 7 |

Description: The "Transfer Funds" functionality is vulnerable to CSRF due to no session-specific random token being attached to the form.

8 | 9 |

Reproduction Steps:

10 | 11 |
    12 |
  1. Go to the Transfer Funds page
  2. 13 |
  3. Submit a funds transfer
  4. 14 |
  5. Note that the only data transmitted is the destination and the amount.
  6. 15 |
16 | 17 |

You can also use the following proof of concept to submit an automatic transfer:

18 | 19 |
<body onload="document.forms[0].submit()">
 20 |     <form action="http://breaker-studentcenter.appspot.com/levels/0/" method="POST">
 21 |         <input type="hidden" name="amount" value="1000000">
 22 |         <input type="hidden" name="to" value="1625">
 23 |     </form>
 24 | </body>
 25 | 
26 | 27 |

Impact: Due to the simple nature of this vulnerability, it's possible for an attacker to transfer funds from any victim whom he can convince to access a page controlled by the attacker. In this proof of concept, it's done via form autosubmission in plain view, but this could be performed in a hidden IFrame, leaving the user no clue that an attack has happened at all.

28 | 29 |

Mitigation: Proper CSRF tokens should be used on all forms. You can read more here: https://www.owasp.org/index.php/Cross-SiteRequestForgery_(CSRF)

30 | 31 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/0

32 | 33 |

Reflected XSS

34 | 35 |

Severity: Critical

36 | 37 |

Description: The amount field of transfers is vulnerable to reflected XSS due to a lack of safe escaping. 38 | This can be triggered via GET (to auto-fill the to and amount fields) or via error cases on POST.

39 | 40 |

Reproduction Steps:

41 | 42 |
    43 |
  1. Go to the Transfer Funds page
  2. 44 |
  3. Enter anything into the to field and in the amount field enter "><script>alert(1);</script>
  4. 45 |
  5. Submit the transfer
  6. 46 |
  7. Note that a script tag has been added to the page; depending on XSS protection settings, you may see an alert box as well
  8. 47 |
48 | 49 |

Impact: This vulnerability allows an attacker to perform any tasks she desires, as an arbitrary user whom she convinces to click a link containing an XSS payload. 50 | This means that an attacker could distribute a payload that causes any user to transfer money to her.

51 | 52 |

Mitigation: All user input must be escaped before displaying to the page, in order to properly mitigate XSS issues. 53 | In this case, it may also be a good idea to convert the amount value to an integer first, as this would completely eliminate the possibility of user input compromising the page.

54 | 55 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/0

56 | 57 |

Direct Object Reference

58 | 59 |

Severity: Critical

60 | 61 |

Description: In addition to the normal to field on transfers, the from field is also accepted, allowing you to specify the account you're transferring from; authentication for the accounts is not required.

62 | 63 |

Reproduction Steps:

64 | 65 |

The following proof of concept performs an automatic transfer from account 1 to account 2, regardless of whether or not you are logged into either account:

66 | 67 |
<body onload="document.forms[0].submit()">
 68 |     <form action="http://breaker-studentcenter.appspot.com/levels/0/" method="POST">
 69 |         <input type="hidden" name="amount" value="1000000">
 70 |         <input type="hidden" name="from" value="1">
 71 |         <input type="hidden" name="to" value="2">
 72 |     </form>
 73 | </body>
 74 | 
75 | 76 |

Impact: Due to the lack of authorization and the ability to directly reference accounts, this makes it trivial for an attacker to transfer funds between any account.

77 | 78 |

Mitigation: The from field should be ignored or -- at the very least -- checked against the account(s) attached to the logged in user.

79 | 80 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/0

81 | 82 |

Level1

83 | 84 |

CSRF Tokens Validated Improperly

85 | 86 |

Severity: Medium

87 | 88 |

Description: While the application uses CSRF tokens, its only validation for them is to ensure that they are 32 characters long. This check is inadequate as any CSRF token (or, indeed, any string of the proper length) will pass the check.

89 | 90 |

Reproduction Steps:

91 | 92 |
    93 |
  1. Submit a wall post
  2. 94 |
  3. Intercept the post with Burp Proxy
  4. 95 |
  5. Change the CSRF token to any other value
  6. 96 |
  7. Note that the post was successful
  8. 97 |
98 | 99 |

Impact: Due to the simple nature of this vulnerability, it's possible for an attacker to post to the wall of any victim whom he can convince to access a page controlled by the attacker.

100 | 101 |

Mitigation: CSRF tokens must be compared in entirety, preferably in constant time to reduce the likelihood of timing attacks.

102 | 103 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/1

104 | 105 |

CSRF Tokens Easily Guessed

106 | 107 |

Severity: Medium

108 | 109 |

Description: While the application uses CSRF tokens, they are not generated randomly, as per standard practice. Instead, they are generated as the MD5 of the user's account nickname. This makes it trivial to guess the CSRF token and falsify it for targeted attacks.

110 | 111 |

Reproduction Steps:

112 | 113 |
    114 |
  1. Look at the CSRF token on the page
  2. 115 |
  3. Run the command echo -n your.nickname | md5sum at the command line
  4. 116 |
  5. Note that the output of this command matches the CSRF token
  6. 117 |
118 | 119 |

Impact: Due to the ease with which these tokens can be guessed, it is trivial for an attacker to perform targeted attacks against a given user.

120 | 121 |

Mitigation: CSRF tokens must be generated randomly upon creation of each user's session.

122 | 123 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/1

124 | 125 |

Stored XSS

126 | 127 |

Severity: Medium

128 | 129 |

Description: Due to improper handling of links, it's possible to embed stored XSS payloads in wall posts.

130 | 131 |

Reproduction Steps:

132 | 133 |
    134 |
  1. Submit a wall post including the URL http://google.com"onmousover="alert(1)
  2. 135 |
  3. Hover over the link in the submitted post
  4. 136 |
  5. Note that an alert dialog is triggered
  6. 137 |
138 | 139 |

Impact: Stored XSS here makes it possible for an attacker to easily impersonate a user's behavior. Due to the fact that the attacker could force a user to make new posts, it's possible that self-sustaining malware could be distributed utilizing this vulnerability.

140 | 141 |

Mitigation: All user input -- including these links -- must be properly HTML escaped before output.

142 | 143 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/1

144 | 145 |

Forced Browsing/Enumerable Post IDs

146 | 147 |

Severity: Low

148 | 149 |

Description: Due to the use of incremental IDs and a lack of authorization checks, it's possible for users to enumerate the posts of others.

150 | 151 |

Reproduction Steps:

152 | 153 |
    154 |
  1. View the page http://breaker-studentcenter.appspot.com/levels/1/post?id=0
  2. 155 |
  3. Note that you see a post by cody.brocious
  4. 156 |
  5. http://breaker-studentcenter.appspot.com/levels/1/post?id=1
  6. 157 |
  7. Note that you see a post by another user
  8. 158 |
  9. Continuing to increment the id will give you every post in the system.
  10. 159 |
160 | 161 |

Impact: An attacker can trivially read every single post in the system, regardless of the user's privacy.

162 | 163 |

Mitigation: If the intention is for posts to be private by default, authorization checks should be put in place to ensure that users are unable to access posts outside their permissions. 164 | In addition, IDs generated in a pseudo-random fashion would eliminate the ability to increment the IDs. 165 | Note well that neither of these conditions is sufficient to completely mitigate the issue; if users are still able to access posts without proper authorization, they can do so even if they can't easily guess post IDs.

166 | 167 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/1

168 | 169 | -------------------------------------------------------------------------------- /gae/static/report01.md: -------------------------------------------------------------------------------- 1 | 2 | Level0 3 | ====== 4 | 5 | Cross-Site Request Forgery 6 | -------------------------- 7 | 8 | **Severity**: Critical 9 | 10 | **Description**: The "Transfer Funds" functionality is vulnerable to CSRF due to no session-specific random token being attached to the form. 11 | 12 | **Reproduction Steps**: 13 | 14 | 1. Go to the Transfer Funds page 15 | 2. Submit a funds transfer 16 | 3. Note that the only data transmitted is the destination and the amount. 17 | 18 | You can also use the following proof of concept to submit an automatic transfer: 19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 | 27 | **Impact**: Due to the simple nature of this vulnerability, it's possible for an attacker to transfer funds from any victim whom he can convince to access a page controlled by the attacker. In this proof of concept, it's done via form autosubmission in plain view, but this could be performed in a hidden IFrame, leaving the user no clue that an attack has happened at all. 28 | 29 | **Mitigation**: Proper CSRF tokens should be used on all forms. You can read more here: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) 30 | 31 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/0 32 | 33 | Reflected XSS 34 | ------------- 35 | 36 | **Severity**: Critical 37 | 38 | **Description**: The `amount` field of transfers is vulnerable to reflected XSS due to a lack of safe escaping. 39 | This can be triggered via GET (to auto-fill the `to` and `amount` fields) or via error cases on POST. 40 | 41 | **Reproduction Steps**: 42 | 43 | 1. Go to the Transfer Funds page 44 | 2. Enter anything into the `to` field and in the amount field enter `">` 45 | 3. Submit the transfer 46 | 4. Note that a script tag has been added to the page; depending on XSS protection settings, you may see an alert box as well 47 | 48 | **Impact**: This vulnerability allows an attacker to perform any tasks she desires, as an arbitrary user whom she convinces to click a link containing an XSS payload. 49 | This means that an attacker could distribute a payload that causes any user to transfer money to her. 50 | 51 | **Mitigation**: All user input must be escaped before displaying to the page, in order to properly mitigate XSS issues. 52 | In this case, it may also be a good idea to convert the amount value to an integer first, as this would completely eliminate the possibility of user input compromising the page. 53 | 54 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/0 55 | 56 | Direct Object Reference 57 | ----------------------- 58 | 59 | **Severity**: Critical 60 | 61 | **Description**: In addition to the normal `to` field on transfers, the `from` field is also accepted, allowing you to specify the account you're transferring from; authentication for the accounts is not required. 62 | 63 | **Reproduction Steps**: 64 | 65 | The following proof of concept performs an automatic transfer from account 1 to account 2, regardless of whether or not you are logged into either account: 66 | 67 | 68 |
69 | 70 | 71 | 72 |
73 | 74 | 75 | **Impact**: Due to the lack of authorization and the ability to directly reference accounts, this makes it trivial for an attacker to transfer funds between any account. 76 | 77 | **Mitigation**: The `from` field should be ignored or -- at the very least -- checked against the account(s) attached to the logged in user. 78 | 79 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/0 80 | 81 | Level1 82 | ======= 83 | 84 | CSRF Tokens Validated Improperly 85 | -------------------------------- 86 | 87 | **Severity**: Medium 88 | 89 | **Description**: While the application uses CSRF tokens, its only validation for them is to ensure that they are 32 characters long. This check is inadequate as any CSRF token (or, indeed, any string of the proper length) will pass the check. 90 | 91 | **Reproduction Steps**: 92 | 93 | 1. Submit a wall post 94 | 2. Intercept the post with Burp Proxy 95 | 3. Change the CSRF token to any other value 96 | 4. Note that the post was successful 97 | 98 | **Impact**: Due to the simple nature of this vulnerability, it's possible for an attacker to post to the wall of any victim whom he can convince to access a page controlled by the attacker. 99 | 100 | **Mitigation**: CSRF tokens must be compared in entirety, preferably in constant time to reduce the likelihood of timing attacks. 101 | 102 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/1 103 | 104 | CSRF Tokens Easily Guessed 105 | -------------------------- 106 | 107 | **Severity**: Medium 108 | 109 | **Description**: While the application uses CSRF tokens, they are not generated randomly, as per standard practice. Instead, they are generated as the MD5 of the user's account nickname. This makes it trivial to guess the CSRF token and falsify it for targeted attacks. 110 | 111 | **Reproduction Steps**: 112 | 113 | 1. Look at the CSRF token on the page 114 | 2. Run the command `echo -n your.nickname | md5sum` at the command line 115 | 3. Note that the output of this command matches the CSRF token 116 | 117 | **Impact**: Due to the ease with which these tokens can be guessed, it is trivial for an attacker to perform targeted attacks against a given user. 118 | 119 | **Mitigation**: CSRF tokens must be generated randomly upon creation of each user's session. 120 | 121 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/1 122 | 123 | Stored XSS 124 | ---------- 125 | 126 | **Severity**: Medium 127 | 128 | **Description**: Due to improper handling of links, it's possible to embed stored XSS payloads in wall posts. 129 | 130 | **Reproduction Steps**: 131 | 132 | 1. Submit a wall post including the URL `http://google.com"onmousover="alert(1)` 133 | 2. Hover over the link in the submitted post 134 | 3. Note that an alert dialog is triggered 135 | 136 | **Impact**: Stored XSS here makes it possible for an attacker to easily impersonate a user's behavior. Due to the fact that the attacker could force a user to make new posts, it's possible that self-sustaining malware could be distributed utilizing this vulnerability. 137 | 138 | **Mitigation**: All user input -- including these links -- must be properly HTML escaped before output. 139 | 140 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/1 141 | 142 | Forced Browsing/Enumerable Post IDs 143 | ----------------------------------- 144 | 145 | **Severity**: Low 146 | 147 | **Description**: Due to the use of incremental IDs and a lack of authorization checks, it's possible for users to enumerate the posts of others. 148 | 149 | **Reproduction Steps**: 150 | 151 | 1. View the page http://breaker-studentcenter.appspot.com/levels/1/post?id=0 152 | 2. Note that you see a post by cody.brocious 153 | 3. http://breaker-studentcenter.appspot.com/levels/1/post?id=1 154 | 4. Note that you see a post by another user 155 | 5. Continuing to increment the id will give you every post in the system. 156 | 157 | **Impact**: An attacker can trivially read every single post in the system, regardless of the user's privacy. 158 | 159 | **Mitigation**: If the intention is for posts to be private by default, authorization checks should be put in place to ensure that users are unable to access posts outside their permissions. 160 | In addition, IDs generated in a pseudo-random fashion would eliminate the ability to increment the IDs. 161 | Note well that neither of these conditions is sufficient to completely mitigate the issue; if users are still able to access posts without proper authorization, they can do so even if they can't easily guess post IDs. 162 | 163 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/1 164 | 165 | -------------------------------------------------------------------------------- /gae/static/report23.html: -------------------------------------------------------------------------------- 1 |

Level2

2 | 3 |

Reflected XSS

4 | 5 |

Severity: Medium

6 | 7 |

Description: The application receives an id field in the query string. In the case of viewing a non-existent profile, this field is not properly encoded before display to the user. In the case of editing profiles, it is ignored but reflected in a non-safe form to the browser as part of the form action.

8 | 9 |

Reproduction Steps:

10 | 11 |
    12 |
  1. Go to http://breaker-studentcenter.appspot.com/levels/2/?id=%3Cscript%3Ealert(1);%3C/script%3E
  2. 13 |
  3. Note that an alert dialog is shown
  4. 14 |
15 | 16 |

Impact: This vulnerability allows an attacker to perform any tasks she desires, as an arbitrary user whom she convinces to click a link containing an XSS payload.

17 | 18 |

Mitigation: All user input must be escaped before displaying to the page, in order to properly mitigate XSS issues. 19 | In this case, it is also a good idea to convert the id parameter to an integer first, as this would completely eliminate the possibility of user input compromising the page.

20 | 21 |

Affected Assets:

22 | 23 |
    24 |
  1. http://breaker-studentcenter.appspot.com/levels/2
  2. 25 |
  3. http://breaker-studentcenter.appspot.com/levels/2/edit
  4. 26 |
27 | 28 |

Stored XSS

29 | 30 |

Severity: Medium

31 | 32 |

Description: The URL for profile photos is not escaped for display, making it vulnerable to stored XSS on both the profile view and edit pages.

33 | 34 |

Reproduction Steps:

35 | 36 |
    37 |
  1. Go to the application's edit profile page
  2. 38 |
  3. In the Profile picture URL field, insert the following: http://breaker-studentcenter.appspot.com/favicon.png?"><script>alert(1);</script>.png
  4. 39 |
  5. Note that the alert dialog is shown upon save
  6. 40 |
41 | 42 |

Impact: This vulnerability allows an attacker to perform any tasks she desires, as an arbitrary user whom she convinces to click a link containing an XSS payload.

43 | 44 |

Mitigation: All user input must be escaped before displaying to the page, in order to properly mitigate XSS issues.

45 | 46 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/2/edit

47 | 48 |

Stored XSS

49 | 50 |

Severity: Medium

51 | 52 |

Description: Colors embedded in the description field of profiles are not escaped when being converted for display.

53 | 54 |

Reproduction Steps:

55 | 56 |
    57 |
  1. Go to the application's edit profile page
  2. 58 |
  3. In the Description field, insert the following: [ red"><script>alert(1);</script> | Exploit ]
  4. 59 |
  5. Note that the alert dialog is shown upon save
  6. 60 |
61 | 62 |

Impact: This vulnerability allows an attacker to perform any tasks she desires, as an arbitrary user whom she convinces to click a link containing an XSS payload.

63 | 64 |

Mitigation: All user input must be escaped before displaying to the page, in order to properly mitigate XSS issues.

65 | 66 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/2/edit

67 | 68 |

Notes

69 | 70 |

The vulnerability count in this level is incorrect, with an actual total of 5, not 7 -- it was based on the number of outputs rather than inputs.

71 | 72 |

The unrelated bonus was due to the handling of special characters in the description. If you're curious, check out https://gist.github.com/daeken/6703071 to see how \x00-\x02 are handled.

73 | 74 |

Level3

75 | 76 |

Cross-Site Request Forgery

77 | 78 |

Severity: High

79 | 80 |

Description: The "Edit Page" functionality is vulnerable to CSRF due to no session-specific random token being attached to the form.

81 | 82 |

Reproduction Steps:

83 | 84 |
    85 |
  1. Go to the admin page
  2. 86 |
  3. Submit a page edit
  4. 87 |
  5. Note that the only data transmitted is the title and the body.
  6. 88 |
89 | 90 |

Impact: Due to the simple nature of this vulnerability, it's possible for an attacker to perform edits on any page belonging to a victim whom he can convince to access a page controlled by the attacker.

91 | 92 |

Mitigation: Proper CSRF tokens should be used on all forms. You can read more here: https://www.owasp.org/index.php/Cross-SiteRequestForgery_(CSRF)

93 | 94 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/3/admin

95 | 96 |

Lack of Authorization on Admin Functionality

97 | 98 |

Severity: Critical

99 | 100 |

Description: The "Edit Page" functionality only checks admin authorization when accessing the form, but does not check on edits.

101 | 102 |

Reproduction Steps:

103 | 104 |
    105 |
  1. As a non-admin, perform a POST to http://breaker-studentcenter.appspot.com/levels/3/admin containing the body title=Vuln&body=No+Admin+Needed
  2. 106 |
  3. Note that the page is edited to reflect these changes
  4. 107 |
108 | 109 |

Impact: Due to the lack of authorization, it's possible for any user to perform arbitrary changes to content. In conjunction with the XSS vulnerabilities, this could allow an attacker to compromise the sessions of every user.

110 | 111 |

Mitigation: Proper authorization must be in place on all actions an administrator could perform.

112 | 113 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/3/admin

114 | 115 |

Authorization by Client-Side Credential

116 | 117 |

Severity: Critical

118 | 119 |

Description: Authorization for the application is done via a cookie named admin. Changing this from 0 to 1 unlocks all admin functionality.

120 | 121 |

Reproduction Steps:

122 | 123 |
    124 |
  1. As a non-admin, visit the application while intercepting responses with Burp Proxy
  2. 125 |
  3. When the server sends the admin cookie, simply change the value to 1
  4. 126 |
  5. Note that the page contains admin functionality, which is fully usable
  6. 127 |
128 | 129 |

Impact: This enables any user to trivially enable administration functionality.

130 | 131 |

Mitigation: User authorization should be stored purely on the server, tied to an authenticated session.

132 | 133 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/3/

134 | 135 |

Stored XSS

136 | 137 |

Severity: Medium

138 | 139 |

Description: Due to improper sanitization of page bodies, it's possible to embed stored XSS payloads in pages.

140 | 141 |

Reproduction Steps:

142 | 143 |
    144 |
  1. As an admin, visit the administration page
  2. 145 |
  3. Put the following in the body: <a ONmouseover="alert(1)">Hover over me</a>
  4. 146 |
  5. Save the page
  6. 147 |
  7. Hover over the inserted text and note that an alert dialog is shown
  8. 148 |
149 | 150 |

Impact: Stored XSS here makes it possible for an attacker to compromise user sessions.

151 | 152 |

Mitigation: All user input must be properly HTML escaped before output. The use of a third-party, vetted library for HTML sanitization is recommended for tags that should be allowed.

153 | 154 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/3/admin

155 | 156 |

Stored XSS

157 | 158 |

Severity: Medium

159 | 160 |

Description: Due to improper sanitization of page titles, it's possible to embed stored XSS payloads in pages.

161 | 162 |

Reproduction Steps:

163 | 164 |
    165 |
  1. As an admin, visit the administration page
  2. 166 |
  3. Put the following in the title field: </title><script>alert(1)</script>
  4. 167 |
  5. Save the page
  6. 168 |
  7. Note that an alert dialog is shown
  8. 169 |
170 | 171 |

Impact: Stored XSS here makes it possible for an attacker to compromise user sessions.

172 | 173 |

Mitigation: All user input must be properly HTML escaped before output.

174 | 175 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/3/admin

176 | 177 |

DOM XSS

178 | 179 |

Severity: Medium

180 | 181 |

Description: Due to improper sanitization of page titles in the window hash, it's possible to inject arbitrary HTML into the page.

182 | 183 |

Reproduction Steps:

184 | 185 |
    186 |
  1. As an admin, visit the page http://breaker-studentcenter.appspot.com/levels/3/#home"><script>alert(1);</script>
  2. 187 |
  3. Note that an alert dialog is shown
  4. 188 |
189 | 190 |

Impact: This makes it possible for an attacker to compromise the session of an admin user whom she can convince to visit the exploited page.

191 | 192 |

Mitigation: All user input must be properly HTML escaped before output on the client side.

193 | 194 |

Affected Assets: http://breaker-studentcenter.appspot.com/levels/3/

195 | -------------------------------------------------------------------------------- /gae/static/report23.md: -------------------------------------------------------------------------------- 1 | Level2 2 | ====== 3 | 4 | Reflected XSS 5 | ------------- 6 | 7 | **Severity**: Medium 8 | 9 | **Description**: The application receives an `id` field in the query string. In the case of viewing a non-existent profile, this field is not properly encoded before display to the user. In the case of editing profiles, it is ignored but reflected in a non-safe form to the browser as part of the form action. 10 | 11 | **Reproduction Steps**: 12 | 13 | 1. Go to http://breaker-studentcenter.appspot.com/levels/2/?id=%3Cscript%3Ealert(1);%3C/script%3E 14 | 2. Note that an alert dialog is shown 15 | 16 | **Impact**: This vulnerability allows an attacker to perform any tasks she desires, as an arbitrary user whom she convinces to click a link containing an XSS payload. 17 | 18 | **Mitigation**: All user input must be escaped before displaying to the page, in order to properly mitigate XSS issues. 19 | In this case, it is also a good idea to convert the `id` parameter to an integer first, as this would completely eliminate the possibility of user input compromising the page. 20 | 21 | **Affected Assets**: 22 | 23 | 1. http://breaker-studentcenter.appspot.com/levels/2 24 | 2. http://breaker-studentcenter.appspot.com/levels/2/edit 25 | 26 | Stored XSS 27 | ----------------------- 28 | 29 | **Severity**: Medium 30 | 31 | **Description**: The URL for profile photos is not escaped for display, making it vulnerable to stored XSS on both the profile view and edit pages. 32 | 33 | **Reproduction Steps**: 34 | 35 | 1. Go to the application's edit profile page 36 | 2. In the `Profile picture URL` field, insert the following: `http://breaker-studentcenter.appspot.com/favicon.png?">.png` 37 | 3. Note that the alert dialog is shown upon save 38 | 39 | **Impact**: This vulnerability allows an attacker to perform any tasks she desires, as an arbitrary user whom she convinces to click a link containing an XSS payload. 40 | 41 | **Mitigation**: All user input must be escaped before displaying to the page, in order to properly mitigate XSS issues. 42 | 43 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/2/edit 44 | 45 | Stored XSS 46 | ----------------------- 47 | 48 | **Severity**: Medium 49 | 50 | **Description**: Colors embedded in the description field of profiles are not escaped when being converted for display. 51 | 52 | **Reproduction Steps**: 53 | 54 | 1. Go to the application's edit profile page 55 | 2. In the `Description` field, insert the following: `[ red"> | Exploit ]` 56 | 3. Note that the alert dialog is shown upon save 57 | 58 | **Impact**: This vulnerability allows an attacker to perform any tasks she desires, as an arbitrary user whom she convinces to click a link containing an XSS payload. 59 | 60 | **Mitigation**: All user input must be escaped before displaying to the page, in order to properly mitigate XSS issues. 61 | 62 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/2/edit 63 | 64 | Notes 65 | ----- 66 | 67 | The vulnerability count in this level is incorrect, with an actual total of 5, not 7 -- it was based on the number of *outputs* rather than *inputs*. 68 | 69 | The unrelated bonus was due to the handling of special characters in the description. If you're curious, check out https://gist.github.com/daeken/6703071 to see how `\x00-\x02` are handled. 70 | 71 | Level3 72 | ====== 73 | 74 | Cross-Site Request Forgery 75 | -------------------------- 76 | 77 | **Severity**: High 78 | 79 | **Description**: The "Edit Page" functionality is vulnerable to CSRF due to no session-specific random token being attached to the form. 80 | 81 | **Reproduction Steps**: 82 | 83 | 1. Go to the admin page 84 | 2. Submit a page edit 85 | 3. Note that the only data transmitted is the title and the body. 86 | 87 | **Impact**: Due to the simple nature of this vulnerability, it's possible for an attacker to perform edits on any page belonging to a victim whom he can convince to access a page controlled by the attacker. 88 | 89 | **Mitigation**: Proper CSRF tokens should be used on all forms. You can read more here: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) 90 | 91 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/3/admin 92 | 93 | Lack of Authorization on Admin Functionality 94 | -------------------------------------------- 95 | 96 | **Severity**: Critical 97 | 98 | **Description**: The "Edit Page" functionality only checks admin authorization when accessing the form, but does not check on edits. 99 | 100 | **Reproduction Steps**: 101 | 102 | 1. As a non-admin, perform a POST to http://breaker-studentcenter.appspot.com/levels/3/admin containing the body `title=Vuln&body=No+Admin+Needed` 103 | 2. Note that the page is edited to reflect these changes 104 | 105 | **Impact**: Due to the lack of authorization, it's possible for any user to perform arbitrary changes to content. In conjunction with the XSS vulnerabilities, this could allow an attacker to compromise the sessions of every user. 106 | 107 | **Mitigation**: Proper authorization must be in place on all actions an administrator could perform. 108 | 109 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/3/admin 110 | 111 | Authorization by Client-Side Credential 112 | --------------------------------------- 113 | 114 | **Severity**: Critical 115 | 116 | **Description**: Authorization for the application is done via a cookie named `admin`. Changing this from `0` to `1` unlocks all admin functionality. 117 | 118 | **Reproduction Steps**: 119 | 120 | 1. As a non-admin, visit the application while intercepting responses with Burp Proxy 121 | 2. When the server sends the `admin` cookie, simply change the value to `1` 122 | 3. Note that the page contains admin functionality, which is fully usable 123 | 124 | **Impact**: This enables any user to trivially enable administration functionality. 125 | 126 | **Mitigation**: User authorization should be stored purely on the server, tied to an authenticated session. 127 | 128 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/3/ 129 | 130 | Stored XSS 131 | ---------- 132 | 133 | **Severity**: Medium 134 | 135 | **Description**: Due to improper sanitization of page bodies, it's possible to embed stored XSS payloads in pages. 136 | 137 | **Reproduction Steps**: 138 | 139 | 1. As an admin, visit the administration page 140 | 2. Put the following in the body: `Hover over me` 141 | 3. Save the page 142 | 4. Hover over the inserted text and note that an alert dialog is shown 143 | 144 | **Impact**: Stored XSS here makes it possible for an attacker to compromise user sessions. 145 | 146 | **Mitigation**: All user input must be properly HTML escaped before output. The use of a third-party, vetted library for HTML sanitization is recommended for tags that should be allowed. 147 | 148 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/3/admin 149 | 150 | Stored XSS 151 | ---------- 152 | 153 | **Severity**: Medium 154 | 155 | **Description**: Due to improper sanitization of page titles, it's possible to embed stored XSS payloads in pages. 156 | 157 | **Reproduction Steps**: 158 | 159 | 1. As an admin, visit the administration page 160 | 2. Put the following in the title field: `` 161 | 3. Save the page 162 | 4. Note that an alert dialog is shown 163 | 164 | **Impact**: Stored XSS here makes it possible for an attacker to compromise user sessions. 165 | 166 | **Mitigation**: All user input must be properly HTML escaped before output. 167 | 168 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/3/admin 169 | 170 | DOM XSS 171 | ------- 172 | 173 | **Severity**: Medium 174 | 175 | **Description**: Due to improper sanitization of page titles in the window hash, it's possible to inject arbitrary HTML into the page. 176 | 177 | **Reproduction Steps**: 178 | 179 | 1. As an admin, visit the page `http://breaker-studentcenter.appspot.com/levels/3/#home">` 180 | 2. Note that an alert dialog is shown 181 | 182 | **Impact**: This makes it possible for an attacker to compromise the session of an admin user whom she can convince to visit the exploited page. 183 | 184 | **Mitigation**: All user input must be properly HTML escaped before output on the client side. 185 | 186 | **Affected Assets**: http://breaker-studentcenter.appspot.com/levels/3/ 187 | -------------------------------------------------------------------------------- /gae/static/report47.md: -------------------------------------------------------------------------------- 1 | Level4 2 | ====== 3 | 4 | Improper Identity Handling 5 | -------------------------- 6 | 7 | **Severity**: Low 8 | 9 | **Description**: Due to a lack of control over usernames, it is possible for multiple usernames to conflict, appearing as the same user. 10 | 11 | **Reproduction Steps**: 12 | 13 | 1. In your Google Account, change your nickname to `daeken` 14 | 2. Make a post on the site 15 | 3. Note that it shows up under the name `daeken`, the administrator for the site 16 | 17 | **Impact**: It is possible for a malicious user to impersonate another user, leading to confusion. 18 | 19 | **Mitigation**: User names should be made unique by storing them locally along with other user data. 20 | 21 | **Affected Assets**: Systemic 22 | 23 | Systemic Information Disclosures 24 | -------------------------------- 25 | 26 | **Severity**: Low 27 | 28 | **Description**: The application is configured to show tracebacks upon unhandled exceptions, revealing system information. 29 | 30 | **Impact**: An attacker may be able to see system paths, code snippets, and other bits of data that could make other attacks easier or viable. 31 | 32 | **Mitigation**: Unhandled exceptions should show an "Internal Error" page rather than a traceback. 33 | 34 | **Affected Assets**: Systemic 35 | 36 | Unchecked Redirects 37 | ------------------- 38 | 39 | **Severity**: Low 40 | 41 | **Description**: The application redirects after submission of votes and deletes using a `from` field in the request. This can be set to any URL, allowing arbitrary redirection. 42 | 43 | **Reproduction Steps**: 44 | 45 | 1. Go to the delete page for a story you have submitted 46 | 2. Change the `from` field in the form to: `http://google.com/` 47 | 3. Submit the deletion 48 | 4. Note that you are redirected to Google's homepage 49 | 50 | **Impact**: An attacker could trick a user into utilizing a fake site, potentially compromising their login credentials. 51 | 52 | **Affected Assets**: 53 | 54 | 1. http://example.com/levels/4/delete 55 | 2. http://example.com/levels/4/vote 56 | 57 | 58 | Reflected XSS 59 | ------------- 60 | 61 | **Severity**: Medium 62 | 63 | **Description**: The application's delete function contains a number of inputs that are not properly escaped: `id`, `type`, and `from`. 64 | 65 | **Reproduction Steps**: 66 | 67 | 1. Go to http://example.com/levels/4/delete?id=4892534685827072&type=Story&from=%22%3E%3Cscript%3Ealert(1)%3B%3C/script%3E 68 | 2. Note that an alert dialog is shown 69 | 70 | **Impact**: This vulnerability allows an attacker to perform any tasks she desires, as an arbitrary user whom she convinces to click a link containing an XSS payload. 71 | 72 | **Mitigation**: All user input must be escaped before displaying to the page, in order to properly mitigate XSS issues. 73 | 74 | **Affected Assets**: http://example.com/levels/4/delete 75 | 76 | Cross-Site Request Forgery 77 | -------------------------- 78 | 79 | **Severity**: High 80 | 81 | **Description**: The voting and deletion functionality are vulnerable to CSRF due to no session-specific random token being attached to the form. 82 | 83 | **Reproduction Steps**: 84 | 85 | 1. Go to the delete page for a story or comment 86 | 2. Note that no CSRF token is included in the request 87 | 88 | **Impact**: Due to the simple nature of this vulnerability, it's possible for an attacker to add students to the account of a victim whom he can convince to access a page controlled by the attacker. 89 | 90 | **Mitigation**: Proper CSRF tokens should be used on all forms and validated upon submission. In addition, in the case of voting, state-changing behavior should not be performed via GET. You can read more here: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) 91 | 92 | **Affected Assets**: 93 | 94 | 1. http://example.com/levels/4/delete 95 | 1. http://example.com/levels/4/vote 96 | 97 | Stored XSS 98 | ---------- 99 | 100 | **Severity**: High 101 | 102 | **Description**: The domain field of submitted stories is not properly escaped when being displayed, making it possible to embed stored XSS payloads in pages. In addition, user nicknames are not escaped for output. 103 | 104 | **Reproduction Steps**: 105 | 106 | 1. Go to http://example.com/levels/4/submit 107 | 2. Enter a title and the following URL: `http://google.com` 108 | 2. Note that an alert dialog is shown 109 | 110 | **Impact**: This vulnerability allows an attacker to perform any tasks she desires, as an arbitrary user whom she convinces to click a link containing an XSS payload. 111 | 112 | **Mitigation**: All user input must be escaped before displaying to the page, in order to properly mitigate XSS issues. 113 | 114 | **Affected Assets**: 115 | 116 | 1. http://example.com/levels/4/submit -- Domain XSS 117 | 2. http://example.com/levels/4/ and http://example.com/levels/4/comments -- User nicknames 118 | 119 | Level5 120 | ====== 121 | 122 | Reflected XSS 123 | ------------- 124 | 125 | **Severity**: Low 126 | 127 | **Description**: The application's browsing and `read` functions receive a `path` field in the query string. In the case of reading a non-existent file or directory, this field is not properly encoded before display to the user. 128 | 129 | **Reproduction Steps**: 130 | 131 | 1. Go to http://example.herokuapp.com/level5/read?path=nonexistent%3Cscript%3Ealert(1);%3C/script%3E 132 | 2. Note that an alert dialog is shown 133 | 134 | **Impact**: This vulnerability allows an attacker to perform any tasks she desires, as an arbitrary user whom she convinces to click a link containing an XSS payload. 135 | 136 | **Mitigation**: All user input must be escaped before displaying to the page, in order to properly mitigate XSS issues. 137 | 138 | **Affected Assets**: 139 | 140 | 1. http://example.herokuapp.com/level5 141 | 2. http://example.herokuapp.com/level5/read 142 | 143 | Directory Traversal 144 | ------------------- 145 | 146 | **Severity**: Critical 147 | 148 | **Description**: The directory browsing function of the application is vulnerable to directory traversal in the `path` field, due to a lack of sanitization on user-provided paths. 149 | 150 | **Reproduction Steps**: 151 | 152 | 1. Go to http://example.herokuapp.com/level5?path=../../../../../../etc 153 | 2. Note that the contents of the `/etc` directory are shown 154 | 155 | **Impact**: This vulnerability allows an attacker to arbitrarily browse the filesystem, revealing the locations and filenames of every file on the system that the web application can access. 156 | 157 | **Mitigation**: Paths constructed using user input should have instances of `../` (and `..\`) removed. Alternatively, detection of such traversal attacks and failing early would potentially make this more resilient. 158 | 159 | **Affected Assets**: http://example.herokuapp.com/level5 160 | 161 | Directory Traversal 162 | ------------------- 163 | 164 | **Severity**: Critical 165 | 166 | **Description**: The file reading function of the application is vulnerable to directory traversal in the `path` field, due to improper sanitization on user-provided paths. 167 | 168 | **Reproduction Steps**: 169 | 170 | 1. Go to http://example.herokuapp.com/level5/read?path=....//....//....//....//....//....//etc/passwd 171 | 2. Note that the contents of the `/etc/passwd` file is shown 172 | 173 | **Impact**: This vulnerability allows an attacker to arbitrarily read files from the filesystem, revealing contents of any file on the system that the web application can access. 174 | 175 | **Mitigation**: Paths constructed using user input should have instances of `../` (and `..\`) removed. Alternatively, detection of such traversal attacks and failing early would potentially make this more resilient. 176 | 177 | **Affected Assets**: http://example.herokuapp.com/level5/read 178 | 179 | Command Injection 180 | ----------------------- 181 | 182 | **Severity**: Critical 183 | 184 | **Description**: Due to a lack of escaping for search queries, it's possible to execute arbitrary commands on the system in the context of the web application. 185 | 186 | **Reproduction Steps**: 187 | 188 | 1. Go to the application 189 | 2. In the `Search in directory` field, insert the following: `" | echo "Arbitrary Code` 190 | 3. Note that the string "Arbitrary Code" is printed 191 | 192 | **Impact**: This vulnerability allows an attacker to execute any command on the server, in the context of the web application. This allows effectively a complete compromise of any security controls put in place. 193 | 194 | **Mitigation**: All user input used in the creation of command lines should be properly quoted and escaped prior to inclusion. When possible, avoid the construction of command lines using user input entirely. 195 | 196 | **Affected Assets**: http://example.herokuapp.com/level5/post_search 197 | 198 | Level6 199 | ====== 200 | 201 | Stored XSS 202 | ---------- 203 | 204 | **Severity**: Medium 205 | 206 | **Description**: Due to improper sanitization of student last names, it's possible to embed stored XSS payloads in pages. 207 | 208 | **Reproduction Steps**: 209 | 210 | 1. Visit the "Add Student" page of the application 211 | 2. Put the following in the `Last name` field: `">` 212 | 3. Add the student 213 | 4. Go to the edit page for the newly added student 214 | 5. Note that an alert dialog is shown 215 | 216 | **Impact**: Stored XSS here makes it possible for an attacker to compromise user sessions. 217 | 218 | **Mitigation**: All user input must be properly HTML escaped before output. 219 | 220 | **Affected Assets**: http://example.herokuapp.com/level6/edit 221 | 222 | Reflected XSS 223 | ------------- 224 | 225 | **Severity**: Low 226 | 227 | **Description**: Due to a lack of escaping on the `id` field on the rows returned from the database, it is possible for an attacker to embed HTML in pages. 228 | 229 | **Reproduction Steps**: 230 | 231 | 1. As an admin, visit the page `http://example.herokuapp.com/level6?filter=+%27%29+UNION+SELECT+%27%3Cscript%3Ealert%281%29%3B%3C%2Fscript%3E%27%2C+1%2C+1+FROM+students+WHERE+%281%3D1+OR+%27test%27%3D%27` 232 | 2. Note that an alert dialog is shown 233 | 234 | **Impact**: An attacker could compromise user sessions and execute code in the context of the page. 235 | 236 | **Mitigation**: In this case, the SQL injection is what makes this attack possible, so mitigating that issue will fix this as well. It is also possible (and recommended) that you escape the data returned by the database. 237 | 238 | **Affected Assets**: http://example.herokuapp.com/level6 239 | 240 | SQL Injection 241 | ------------- 242 | 243 | **Severity**: Critical 244 | 245 | **Description** Due to a lack of escaping on the following fields, it is possible to execute arbitrary SQL in the application. 246 | 247 | - ID parameter on the edit page 248 | - `firstname` and `lastname` fields to post_add 249 | - `filter` field on index page 250 | 251 | **Reproduction Steps**: See 'Reflected XSS' for an example 252 | 253 | **Impact**: An attacker can arbitrarily read and write to the database, compromising data integrity and confidentiality, in addition to potentially escalating privileges to circumvent security controls. 254 | 255 | **Affected Assets**: 256 | 257 | 1. http://example.herokuapp.com/level6 258 | 2. http://example.herokuapp.com/level6/post_add 259 | 3. http://example.herokuapp.com/level6/edit 260 | 261 | Cross-Site Request Forgery 262 | -------------------------- 263 | 264 | **Severity**: High 265 | 266 | **Description**: The "Add Student" functionality is vulnerable to CSRF due to a lack of CSRF token validation. 267 | 268 | **Reproduction Steps**: 269 | 270 | 1. Go to the "Add Students" page 271 | 2. Change or modify the CSRF token value in the form 272 | 3. Submit the form 273 | 4. Note that the student is added despite the token mismatch 274 | 275 | **Impact**: Due to the simple nature of this vulnerability, it's possible for an attacker to add students to the account of a victim whom he can convince to access a page controlled by the attacker. 276 | 277 | **Mitigation**: Proper CSRF tokens should be used on all forms and validated upon submission. You can read more here: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) 278 | 279 | **Affected Assets**: http://example.herokuapp.com/level6/post_add 280 | 281 | Level7 282 | ====== 283 | 284 | Reflected XSS 285 | ------------- 286 | 287 | **Severity**: Low 288 | 289 | **Description**: The application shows the previously submitted username and password upon login failure. The password field is not properly escaped when reflected to the user. 290 | 291 | **Reproduction Steps**: 292 | 293 | 1. Go to http://example.herokuapp.com/level7 294 | 2. Submit any username with the password: `">` 295 | 3. Note that an alert dialog is shown 296 | 297 | **Impact**: This vulnerability allows an attacker to perform any tasks she desires, as an arbitrary user whom she convinces to click a link containing an XSS payload. 298 | 299 | **Mitigation**: All user input must be escaped before displaying to the page, in order to properly mitigate XSS issues. 300 | 301 | **Affected Assets**: http://example.herokuapp.com/level7/post_index 302 | 303 | SQL Injection 304 | ------------- 305 | 306 | **Severity**: Critical 307 | 308 | **Description** Due to a lack of escaping on username field, it is possible to execute arbitrary SQL in the application. 309 | 310 | **Reproduction Steps**: 311 | 312 | 1. Go to http://example.herokuapp.com/level7 313 | 2. Enter the password `password` and the following username: `' UNION SELECT 'password` 314 | 3. Note that you are successfully logged in 315 | 316 | **Impact**: An attacker can arbitrarily read and write to the database, compromising data integrity and confidentiality, in addition to potentially escalating privileges to circumvent security controls. 317 | 318 | **Affected Assets**: http://example.herokuapp.com/level7/post_index 319 | -------------------------------------------------------------------------------- /gae/templates/home.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacker0x01/Hacker101Coursework/822efa707eaca9094720c26dd183e0a1d8ba63e7/gae/templates/home.html -------------------------------------------------------------------------------- /gae/templates/level0/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | First Bank of Breakerville 5 | 6 | 14 | 15 | 16 |

Breakerbank

17 | Your account number: ${ acct_num }
18 | % if message: 19 |
20 | ${ message | h } 21 | % endif 22 | % if error: 23 |
24 | ${ error | h } 25 | % endif 26 |
27 |
28 |

Transfer Funds

29 | Destination account:
30 | Amount: 31 |
32 |
33 | 34 |
35 |
36 | You can send friends your personal payment link to allow them to easily send you money!
37 |
38 | % if error == 'Amounts must be greater than zero': 39 | Sample bug report for this level 40 | % else: 41 | 42 | % endif 43 | 44 | -------------------------------------------------------------------------------- /gae/templates/level1/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Breakbook 5 | 6 | 14 | 15 | 16 |

Breakbook

17 |
18 | What's on your mind?
19 |
20 | HTML disallowed, but links will be clickable.
21 | 22 | 23 |
24 |

Feed

25 | 26 | % for i, post in posts: 27 | 28 | 29 | 30 | 31 | 32 | 33 | % endfor 34 | 35 | 36 | 37 | 38 | 39 |
${ post.by.nickname() | h }${ post.contents }Permalink
AdminDon't forget to check out https://hackerone.com/ for all the latest news!
40 | 41 | 42 | -------------------------------------------------------------------------------- /gae/templates/level1/post.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Post ${ post.key().id() } — Breakbook 5 | 6 | 7 | 8 |

Breakbook

9 | 10 | 11 | 12 | 13 | 14 | 15 |
${ post.by.nickname() | h }${ post.contents }
16 | 17 | -------------------------------------------------------------------------------- /gae/templates/level1/posted.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Breakbook 5 | 6 | 7 | 8 |

Breakbook

9 | Post succeeded.
10 | % if message != None: 11 |

${ message | h }

12 | % endif 13 | Return 14 | 15 | -------------------------------------------------------------------------------- /gae/templates/level2/edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Edit Breaker Profile 5 | 6 | 7 | 8 |

Edit Breaker Profile

9 |
10 | 11 |

Profile picture URL:

12 |

Nickname:

13 |

Description:

14 | 15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /gae/templates/level2/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Breaker Profile 5 | 6 | 14 | 15 | 16 |

Your Breaker Profile

17 | 18 |

${ profile.nickname | h }

19 |

Description: ${ profile.html_desc() }

20 | % if editable: 21 | Edit your profile 22 | % endif 23 | Link to your profile 24 | 25 | -------------------------------------------------------------------------------- /gae/templates/level3/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Breaker CMS Admin 5 | 6 | 7 | 8 |

Edit Page

9 |
10 |

Page Title:

11 |

Page Body:

12 |

Note: New HTML filtering is in force. You can add links, but they can't use DOM events. Scripts are forbidden for safety reasons.

13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /gae/templates/level3/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ${ page.title } 5 | 6 | 14 | 15 | 16 |

${ page.title | h }

17 |

${ page.html_body }

18 | 31 | 32 | -------------------------------------------------------------------------------- /gae/templates/level4/comments.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ${ story.title | h } — Breaker News 5 | 6 | 7 | 8 |
9 | 10 | 21 | 22 | 23 | 41 | 42 | 43 | 50 | 51 | % for comment in story.comments: 52 | 53 | 79 | 80 | % endfor 81 |
11 | 12 | 13 | 14 | 15 | 18 | 19 |
Breaker NewsSubmit 16 | ${ user.nickname() | h } (${ curuser.karma }) 17 |
20 |
24 | 25 | 26 | 31 | 38 | 39 |
27 | % if story.user != user and not dbuser.voted_on(story.key().id()): 28 | 29 | % endif 30 | 32 | ${ story.title | h } (${ story.domain })
33 | ${ story.votes } points by ${ story.user.nickname() } 34 | % if story.user == user: 35 | — delete 36 | % endif 37 |
40 |
44 |
45 |
46 | 47 | 48 |
49 |
54 | 55 | 56 | 62 | 68 | 69 | 70 | 71 | 76 | 77 |
57 | % if comment.user != user and not dbuser.voted_on(comment.key().id()): 58 | 59 | 60 | % endif 61 | 63 | ${ comment.votes } points by ${ comment.user.nickname() } 64 | % if comment.user == user: 65 | — delete 66 | % endif 67 |
  72 | % for part in comment.content.split('\n'): 73 |

${ part | h}

74 | % endfor 75 |
78 |
82 | 83 | -------------------------------------------------------------------------------- /gae/templates/level4/delete.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Breaker News 5 | 6 | 7 | 8 |
9 | 10 | 21 | 22 | 23 | 34 | 35 |
11 | 12 | 13 | 14 | 15 | 18 | 19 |
Breaker NewsSubmit 16 | ${ user.nickname() | h } (${ curuser.karma }) 17 |
20 |
24 |
25 | Delete the requested content? 26 |
27 |
28 | 29 | 30 | 31 | 32 |
33 |
36 | 37 | -------------------------------------------------------------------------------- /gae/templates/level4/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Breaker News 5 | 6 | 11 | 21 | 22 | 23 |
24 | 25 | 36 | 37 | 38 |
26 | 27 | 28 | 29 | 30 | 33 | 34 |
Breaker NewsSubmit 31 | ${ user.nickname() | h } (${ curuser.karma }) 32 |
35 |
39 | 40 | % for story in stories: 41 | 42 | 47 | 54 | 55 | % endfor 56 |
43 | % if story.user != user and not dbuser.voted_on(story.key().id()): 44 | 45 | % endif 46 | 48 | ${ story.title | h } (${ story.domain })
49 | ${ story.votes } points by ${ story.user.nickname() }comments 50 | % if story.user == user: 51 | delete 52 | % endif 53 |
57 | 58 | 59 |
60 | 61 | -------------------------------------------------------------------------------- /gae/templates/level4/submit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Breaker News 5 | 6 | 7 | 8 |
9 | 10 | 21 | 22 | 23 | 33 | 34 |
11 | 12 | 13 | 14 | 15 | 18 | 19 |
Breaker NewsSubmit 16 | ${ user.nickname() | h } (${ curuser.karma }) 17 |
20 |
24 |
25 |
26 | Title: (80 characters max)
27 | URL:
28 | 29 |
30 | 31 |
32 |
35 | 36 | -------------------------------------------------------------------------------- /levels58/Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn main:app 2 | -------------------------------------------------------------------------------- /levels58/db.py: -------------------------------------------------------------------------------- 1 | import MySQLdb as mdb 2 | 3 | sql = dict( 4 | host='...', 5 | username='...', 6 | password='...', 7 | db='...' 8 | ) 9 | 10 | class DB(object): 11 | def __init__(self): 12 | self.conn = mdb.connect(sql['host'], sql['username'], sql['password'], sql['db']) 13 | 14 | def query(self, query, *args): 15 | cursor = None 16 | try: 17 | with self.conn: 18 | cursor = self.conn.cursor() 19 | print '[DEBUG] %s <= %r' % (query, args) 20 | cursor.execute(query, args) 21 | 22 | return cursor.fetchall() 23 | except mdb.OperationalError, e: 24 | try: 25 | if cursor is not None: 26 | cursor.close() 27 | except: 28 | pass 29 | if 'server has gone away' in str(e): 30 | self.conn = mdb.connect(sql['host'], sql['username'], sql['password'], sql['db']) 31 | return self.query(query, *args) 32 | else: 33 | raise 34 | 35 | def hastable(self, table): 36 | try: 37 | print 'Checking to see if table %s exists' % table 38 | self.query('SELECT id FROM `%s` LIMIT 1;' % table) 39 | print 'Successful query ... ?' 40 | return True 41 | except: 42 | import traceback 43 | traceback.print_exc() 44 | return False 45 | 46 | def maketable(self, table, **kwargs): 47 | if len(kwargs): 48 | elems = ', ' + ', '.join('%s %s' % (k, v) for k, v in kwargs.items()) 49 | else: 50 | elems = '' 51 | self.query('CREATE TABLE `%s`(id int not null auto_increment, primary key(id)%s);' % (table, elems)) 52 | 53 | db = DB() 54 | -------------------------------------------------------------------------------- /levels58/el12_sandbox/keep_me: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacker0x01/Hacker101Coursework/822efa707eaca9094720c26dd183e0a1d8ba63e7/levels58/el12_sandbox/keep_me -------------------------------------------------------------------------------- /levels58/handler.py: -------------------------------------------------------------------------------- 1 | import os 2 | from json import dumps 3 | from flask import abort, make_response, render_template, request, session, Response 4 | from flask import redirect as _redirect 5 | from werkzeug.exceptions import HTTPException 6 | from urllib import quote, urlencode 7 | from db import db 8 | from datetime import datetime 9 | 10 | class DictObject(dict): 11 | pass 12 | 13 | class StrObject(str): 14 | pass 15 | 16 | all = {} 17 | 18 | def handler(_tpl=None, _json=False, CSRFable=False): 19 | def sub(func): 20 | ofunc = func 21 | while hasattr(func, '__delegated__'): 22 | func = func.__delegated__ 23 | sofunc = func 24 | 25 | name = func.func_name 26 | rpc = False 27 | tpl = _tpl 28 | json = _json 29 | if name.startswith('get_'): 30 | name = name[4:] 31 | method = 'GET' 32 | elif name.startswith('post_'): 33 | method = 'POST' 34 | elif name.startswith('rpc_'): 35 | method = 'POST' 36 | rpc = json = True 37 | tpl = None 38 | else: 39 | raise Exception('All handlers must be marked get_, post_, or rpc_.') 40 | 41 | module = func.__module__.split('.')[-1] 42 | if not module in all: 43 | all[module] = DictObject() 44 | setattr(handler, module, all[module]) 45 | args = func.__code__.co_varnames[:func.__code__.co_argcount] 46 | hasId = len(args) > 0 and args[0] == 'id' and not rpc 47 | 48 | def func(id=None): 49 | if 'csrf' not in session: 50 | token = os.urandom(16) 51 | session['csrf'] = ''.join('%02x' % ord(c) for c in token) 52 | if not CSRFable and method == 'POST' and \ 53 | ('csrf' not in request.form or request.form['csrf'] != session['csrf']): 54 | abort(403) 55 | params = request.form if method == 'POST' else request.args 56 | kwargs = {} 57 | for i, arg in enumerate(args): 58 | if i == 0 and arg == 'id' and not rpc: 59 | continue 60 | if arg in params: 61 | kwargs[arg] = params[arg] 62 | elif arg in request.files: 63 | kwargs[arg] = request.files[arg] 64 | else: 65 | assert not rpc # RPC requires all arguments. 66 | 67 | try: 68 | if hasId and id != None: 69 | ret = ofunc(int(id), **kwargs) 70 | else: 71 | ret = ofunc(**kwargs) 72 | except RedirectException, r: 73 | return _redirect(r.url) 74 | if json: 75 | ret = dumps(ret) 76 | elif tpl != None: 77 | if isinstance(ret, str) or isinstance(ret, unicode): 78 | return ret 79 | if ret == None: 80 | ret = {} 81 | sret = ret 82 | ret = kwargs 83 | ret['handler'] = handler 84 | ret['request'] = request 85 | ret['session'] = session 86 | ret['len'] = len 87 | ret.update(sret) 88 | ret = render_template(tpl + '.html', **ret) 89 | csrf = '' % session['csrf'] 90 | ret = ret.replace('$CSRF$', csrf) 91 | 92 | if hasattr(request, '_headers'): 93 | ret = make_response(ret, 200) 94 | for k, v in request._headers.items(): 95 | ret.headers[k] = v 96 | 97 | return ret 98 | 99 | func.func_name = '__%s__%s__' % (module, name) 100 | 101 | def url(_id=None, **kwargs): 102 | if module == 'index': 103 | url = '/' 104 | trailing = True 105 | else: 106 | url = '/%s' % module 107 | trailing = False 108 | if name != 'index': 109 | if not trailing: 110 | url += '/' 111 | url += '%s' % name 112 | trailing = False 113 | if _id != None: 114 | if not trailing: 115 | url += '/' 116 | url += quote(str(_id)) 117 | if len(kwargs): 118 | url += '?' 119 | url += urlencode(dict((k, str(v)) for k, v in kwargs.items())) 120 | return url 121 | 122 | ustr = StrObject(url()) 123 | ustr.__call__ = ofunc 124 | ustr.url = url 125 | func.url = url 126 | if not name in all[module]: 127 | all[module][name] = method, args, rpc, [None, None] 128 | if hasId and not rpc: 129 | all[module][name][3][1] = func 130 | else: 131 | all[module][name][3][0] = func 132 | setattr(all[module], sofunc.func_name, ustr) 133 | return ustr 134 | 135 | if _tpl != None and hasattr(_tpl, '__call__'): 136 | func = _tpl 137 | _tpl = None 138 | return sub(func) 139 | return sub 140 | 141 | def sessid(): 142 | if 'sessid' not in session: 143 | token = os.urandom(8) 144 | session['sessid'] = ''.join('%02x' % ord(c) for c in token) 145 | return session['sessid'] 146 | handler.sessid = sessid 147 | 148 | def header(key, value): 149 | if not hasattr(request, '_headers'): 150 | request._headers = {} 151 | 152 | request._headers[key] = value 153 | handler.header = header 154 | 155 | class RedirectException(Exception): 156 | def __init__(self, url): 157 | self.url = url 158 | 159 | def redirect(url, _id=None, **kwargs): 160 | if hasattr(url, '__call__') and hasattr(url, 'url'): 161 | url = url.url(_id, **kwargs) 162 | print 'Redirecting to', url 163 | raise RedirectException(url) 164 | 165 | def exam1_auth(level, check=False): 166 | if check: 167 | if 'userid' not in session: 168 | return False 169 | 170 | iso, dlevel = db.query('SELECT creation, level FROM exam1_users WHERE id=%s', session['userid'])[0] 171 | try: 172 | date = datetime.strptime(iso, '%Y-%m-%dT%H:%M:%S') 173 | except: 174 | date = datetime.strptime(iso, '%Y-%m-%dT%H:%M:%S.%f') 175 | 176 | if dlevel < level: 177 | return False 178 | 179 | if (datetime.now() - date).total_seconds() > 14400: 180 | return False 181 | 182 | return True 183 | 184 | def xsub(func): 185 | def sub(*args, **kwargs): 186 | if 'userid' not in session: 187 | redirect(handler.exam1.get_index.url(error='Session expired')) 188 | 189 | iso, dlevel = db.query('SELECT creation, level FROM exam1_users WHERE id=%s', session['userid'])[0] 190 | try: 191 | date = datetime.strptime(iso, '%Y-%m-%dT%H:%M:%S') 192 | except: 193 | date = datetime.strptime(iso, '%Y-%m-%dT%H:%M:%S.%f') 194 | 195 | if dlevel < level: 196 | redirect(handler.exam1.get_index.url(error='You have not yet achieved this level')) 197 | 198 | if (datetime.now() - date).total_seconds() > 14400: 199 | redirect(handler.exam1.get_index.url(error='Time has expired!')) 200 | 201 | return func(*args, **kwargs) 202 | 203 | sub.__delegated__ = func 204 | return sub 205 | return xsub 206 | 207 | int_ = int 208 | def int(v): 209 | try: 210 | return int_(v) 211 | except: 212 | return 0 213 | -------------------------------------------------------------------------------- /levels58/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | import level5, level6, level7, level8 2 | import exam1, el10, el11, el12, el13 3 | -------------------------------------------------------------------------------- /levels58/handlers/el10.py: -------------------------------------------------------------------------------- 1 | from handler import * 2 | from handler import exam1_auth as auth 3 | 4 | @handler('exam1/el10/index') 5 | @auth(0) 6 | def get_index(message=None): 7 | return dict(message=message, posts=db.query('SELECT id, name, email, body, date FROM el10_post WHERE owner=%s ORDER BY id DESC', session['userid'])) 8 | 9 | @handler(CSRFable=True) 10 | @auth(0) 11 | def post_index(name, email, body): 12 | db.query('INSERT INTO el10_post (name, email, body, date, owner) VALUES (%s, %s, %s, %s, %s)', name, email, body, str(datetime.now()), session['userid']) 13 | 14 | redirect(get_index) 15 | 16 | @handler('exam1/el10/admin_login') 17 | @auth(0) 18 | def get_admin_login(): 19 | pass 20 | 21 | @handler 22 | @auth(0) 23 | def post_admin_login(username, password): 24 | if len(db.query("SELECT id FROM el10_admins WHERE username='%s' AND password='%s'" % (username, password))): 25 | session['el10_admin'] = True 26 | if db.query('SELECT level FROM exam1_users WHERE id=%s', session['userid'])[0][0] == 0: 27 | db.query('UPDATE exam1_users SET level=1 WHERE id=%s', session['userid']) 28 | redirect(get_index.url(message='Exam 1 level 1 unlocked!')) 29 | else: 30 | redirect(get_index) 31 | else: 32 | redirect(get_admin_login) 33 | 34 | @handler 35 | def get_admin_logout(): 36 | session['el10_admin'] = False 37 | redirect(get_index) 38 | 39 | @handler 40 | @auth(0) 41 | def get_delete(id): 42 | if len(db.query('SELECT id FROM el10_post WHERE id=%s AND owner=%s', id, session['userid'])) == 0: 43 | return 'Post does not exist.' 44 | 45 | db.query('DELETE FROM el10_post WHERE id=%s', id) 46 | 47 | redirect(get_index) 48 | 49 | if not db.hastable('el10_post'): 50 | db.maketable('el10_post', 51 | name='VARCHAR(1024)', 52 | email='VARCHAR(1024)', 53 | body='VARCHAR(1024)', 54 | date='VARCHAR(1024)', 55 | owner='INT' 56 | ) 57 | if not db.hastable('el10_admins'): 58 | db.maketable('el10_admins', 59 | username='VARCHAR(1024)', 60 | password='VARCHAR(1024)' 61 | ) 62 | -------------------------------------------------------------------------------- /levels58/handlers/el11.py: -------------------------------------------------------------------------------- 1 | from handler import * 2 | from handler import exam1_auth as auth 3 | import commands 4 | 5 | @handler('exam1/el11/index') 6 | @auth(1) 7 | def get_index(message=None): 8 | if len(getsettings()) == 0: 9 | savesettings(dict( 10 | ssid='breaksys', 11 | int_ip='192.168.1.100', 12 | admin_username='admin', 13 | admin_password='admin' 14 | )) 15 | return dict(admin='el11_admin' in session and session['el11_admin'], message=message, settings=getsettings()) 16 | 17 | @handler 18 | @auth(1) 19 | def post_login(username, password): 20 | settings = getsettings() 21 | if username == settings['admin_username'] and password == settings['admin_password']: 22 | session['el11_admin'] = True 23 | redirect(get_index) 24 | 25 | @handler 26 | @auth(1) 27 | def get_logout(): 28 | session['el11_admin'] = False 29 | redirect(get_index) 30 | 31 | @handler(CSRFable=True) 32 | @auth(1) 33 | def post_set(k, v): 34 | savesettings({k : v}) 35 | 36 | if 'el11_admin' not in session or session['el11_admin'] == False: 37 | if db.query('SELECT level FROM exam1_users WHERE id=%s', session['userid'])[0][0] == 1: 38 | db.query('UPDATE exam1_users SET level=2 WHERE id=%s', session['userid']) 39 | redirect(get_index.url(message='Exam 1 level 2 unlocked!')) 40 | 41 | redirect(get_index) 42 | 43 | @handler 44 | @auth(1) 45 | def get_diag(cmd, param0='', param1='', param2='', param3=''): 46 | def escape(param): 47 | return param.replace('"', '\\"') 48 | args = [cmd, param0, param1, param2, param3] 49 | args = ['"%s"' % escape(arg) for arg in args if arg != ''] 50 | return commands.getoutput(' '.join(args)).replace('\n', '\n
') 51 | 52 | def getsettings(): 53 | return dict((k, v) for k, v in db.query('SELECT _key, value FROM el11_settings WHERE owner=%s', session['userid'])) 54 | 55 | def savesettings(settings): 56 | for k, v in settings.items(): 57 | if len(db.query('SELECT value FROM el11_settings WHERE owner=%s AND _key=%s', session['userid'], k)): 58 | db.query('UPDATE el11_settings SET value=%s WHERE owner=%s AND _key=%s', v, session['userid'], k) 59 | else: 60 | db.query('INSERT INTO el11_settings (owner, _key, value) VALUES (%s, %s, %s)', session['userid'], k, v) 61 | 62 | if not db.hastable('el11_settings'): 63 | db.maketable('el11_settings', 64 | owner='INT', 65 | _key='VARCHAR(1024)', 66 | value='VARCHAR(1024)' 67 | ) 68 | -------------------------------------------------------------------------------- /levels58/handlers/el12.py: -------------------------------------------------------------------------------- 1 | from handler import * 2 | from handler import exam1_auth as auth 3 | import os, re 4 | 5 | def format(body): 6 | def rep(match): 7 | title = match.group(1) 8 | return '%s' % (title, title) 9 | body = body.replace('&', '&').replace('"', '"').replace('<', '<').replace('>', '>') 10 | body = re.sub(r'\[\[(.*?)\]\]', rep, body) 11 | return body.replace('\n', '
') 12 | 13 | @handler('exam1/el12/index') 14 | @auth(2) 15 | def get_index(page='Home'): 16 | path = 'el12_sandbox/%i/%s' % (session['userid'], page) 17 | if page == 'Home' and not os.path.exists(path): 18 | try: 19 | os.mkdir('el12_sandbox/%i' % session['userid']) 20 | except: 21 | pass 22 | with file(path, 'w') as fp: 23 | fp.write('Welcome to [[BreakerWiki]]!\n\nEnjoy your time here.') 24 | if os.path.exists(path): 25 | return dict(found=True, page=page, body=format(file(path, 'r').read())) 26 | else: 27 | return dict(found=False, page=page) 28 | 29 | @handler('exam1/el12/edit') 30 | @auth(2) 31 | def get_edit(page): 32 | path = 'el12_sandbox/%i/%s' % (session['userid'], page) 33 | if not os.path.exists(path): 34 | redirect(get_create.url(page=page)) 35 | return dict(body=file(path).read()) 36 | 37 | @handler 38 | @auth(2) 39 | def post_edit(page, body): 40 | if '../' in page: 41 | if db.query('SELECT level FROM exam1_users WHERE id=%s', session['userid'])[0][0] == 2: 42 | db.query('UPDATE exam1_users SET level=3 WHERE id=%s', session['userid']) 43 | return 'Writing to files outside the sandbox is forbidden. But level 3 is now unlocked!' 44 | 45 | with file('el12_sandbox/%i/%s' % (session['userid'], page), 'w') as fp: 46 | fp.write(body) 47 | 48 | redirect(get_index.url(page=page)) 49 | 50 | @handler('exam1/el12/create') 51 | @auth(2) 52 | def get_create(page=''): 53 | pass 54 | -------------------------------------------------------------------------------- /levels58/handlers/el13.py: -------------------------------------------------------------------------------- 1 | from handler import * 2 | from handler import exam1_auth as auth 3 | 4 | @handler('exam1/el13/index') 5 | @auth(3) 6 | def get_index(message=None): 7 | pass 8 | 9 | @handler 10 | @auth(3) 11 | def post_message(name, message): 12 | db.query("INSERT INTO el13_messages (owner, name, message) VALUES (%i, '%s', '%s')" % (session['userid'], name, message)) 13 | 14 | redirect(get_index.url(message='Message received!')) 15 | 16 | @handler('exam1/el13/login') 17 | @auth(3) 18 | def get_login(): 19 | pass 20 | 21 | @handler 22 | @auth(3) 23 | def post_login(username, password): 24 | if len(db.query("SELECT id FROM el13_admins WHERE username=%s AND password=%s", username, password)): 25 | redirect(get_feedback) 26 | else: 27 | redirect(get_login) 28 | 29 | @handler('exam1/el13/feedback') 30 | @auth(3) 31 | def get_feedback(): 32 | return dict(messages=db.query('SELECT name, message FROM el13_messages ORDER BY id DESC')) 33 | 34 | if not db.hastable('el13_messages'): 35 | db.maketable('el13_messages', 36 | owner='INT', 37 | name='VARCHAR(1024)', 38 | message='VARCHAR(1024)' 39 | ) 40 | 41 | if not db.hastable('el13_admins'): 42 | db.maketable('el13_admins', 43 | username='VARCHAR(1024)', 44 | password='VARCHAR(1024)' 45 | ) 46 | -------------------------------------------------------------------------------- /levels58/handlers/exam1.py: -------------------------------------------------------------------------------- 1 | from handler import * 2 | from handler import exam1_auth as auth 3 | 4 | @handler('exam1/index') 5 | def get_index(error=None): 6 | if auth(0, check=True): 7 | redirect(get_authed) 8 | return dict(error=error) 9 | 10 | @handler 11 | def post_create_user(username, password, confirm): 12 | if db.query('SELECT COUNT(id) FROM exam1_users WHERE username=%s', username)[0][0] == 1: 13 | redirect(get_index.url(error='Username is taken')) 14 | 15 | if password != confirm: 16 | redirect(get_index.url(error='Passwords do not match')) 17 | 18 | db.query('INSERT INTO exam1_users (username, password, creation, level) VALUES (%s, %s, %s, 0)', username, password, datetime.now().isoformat()) 19 | 20 | session['userid'] = db.query('SELECT id FROM exam1_users WHERE username=%s', username)[0][0] 21 | 22 | redirect(get_authed) 23 | 24 | @handler 25 | def post_login(username, password): 26 | data = db.query('SELECT id FROM exam1_users WHERE username=%s AND password=%s', username, password) 27 | if len(data) == 1: 28 | session['userid'] = data[0][0] 29 | redirect(get_authed) 30 | else: 31 | redirect(get_index.url(error='Username/password incorrect')) 32 | 33 | @handler('exam1/authed') 34 | @auth(0) 35 | def get_authed(): 36 | iso, level = db.query('SELECT creation, level FROM exam1_users WHERE id=%s', session['userid'])[0] 37 | try: 38 | date = datetime.strptime(iso, '%Y-%m-%dT%H:%M:%S') 39 | except: 40 | date = datetime.strptime(iso, '%Y-%m-%dT%H:%M:%S.%f') 41 | delta = 14400 - (datetime.now() - date).seconds 42 | return dict(hours=delta // 60 // 60, minutes=(delta // 60) % 60, level=level) 43 | 44 | if not db.hastable('exam1_users'): 45 | db.maketable('exam1_users', 46 | username='VARCHAR(1024)', 47 | password='VARCHAR(1024)', 48 | creation='VARCHAR(1024)', 49 | level='INT', 50 | ) 51 | -------------------------------------------------------------------------------- /levels58/handlers/level5.py: -------------------------------------------------------------------------------- 1 | import commands, os 2 | from handler import * 3 | from glob import glob 4 | from os.path import isfile, isdir 5 | 6 | @handler('level5/index') 7 | def get_index(path='/'): 8 | if not isdir('level5_docs/' + path): 9 | return 'No such directory: ' + path 10 | 11 | if not path.endswith('/'): 12 | path += '/' 13 | dirs = [] 14 | files = [] 15 | for fn in glob('level5_docs/' + path + '*'): 16 | if isdir(fn): 17 | dirs.append(fn.rsplit('/', 1)[1]) 18 | else: 19 | files.append(fn.rsplit('/', 1)[1]) 20 | 21 | return dict(path=path, dirs=dirs, files=files) 22 | 23 | @handler 24 | def get_read(path): 25 | path = path.replace('../', '') 26 | try: 27 | return Response(file('level5_docs/' + path).read(), mimetype='text/plain') 28 | except: 29 | return 'No such file: ' + path 30 | 31 | @handler 32 | def post_search(path, text): 33 | old = os.getcwd() 34 | try: 35 | os.chdir('level5_docs/' + path) 36 | out = commands.getoutput('grep -r "%s" .' % text) 37 | finally: 38 | os.chdir(old) 39 | return out.replace('<', '<').replace('>', '>').replace('\n', '
') 40 | -------------------------------------------------------------------------------- /levels58/handlers/level6.py: -------------------------------------------------------------------------------- 1 | from handler import * 2 | 3 | @handler('level6/index') 4 | def get_index(filter=''): 5 | if db.query('SELECT COUNT(id) FROM students WHERE sessid=%s;', handler.sessid())[0][0] == 0: 6 | def add(firstname, lastname): 7 | db.query('INSERT INTO `students` (firstname, lastname, sessid) VALUES (%s, %s, %s);', firstname, lastname, handler.sessid()) 8 | 9 | add('John', 'Doe') 10 | add('Cody', 'Brocious') 11 | add('Testy', 'McTesterson') 12 | 13 | print filter 14 | return dict(filter=filter, students=db.query("SELECT id, lastname, firstname FROM students WHERE sessid='%s' AND (firstname LIKE '%%%%%s%%%%' OR lastname LIKE '%%%%%s%%%%');" % (handler.sessid(), filter, filter))) 15 | 16 | @handler('level6/edit') 17 | def get_edit(id): 18 | return dict(student=db.query("SELECT id, lastname, firstname FROM students WHERE id='%s';" % id)[0]) 19 | 20 | @handler 21 | def post_edit(id, firstname, lastname): 22 | student = db.query('SELECT sessid FROM students where id=%s', id) 23 | if student[0][0] != handler.sessid(): 24 | return 'Student does not belong to your account.' 25 | 26 | db.query('UPDATE students SET lastname=%s, firstname=%s WHERE id=%s', lastname, firstname, id) 27 | 28 | redirect(get_index) 29 | 30 | @handler('level6/add') 31 | def get_add(): 32 | pass 33 | 34 | @handler(CSRFable=True) 35 | def post_add(firstname, lastname): 36 | db.query("INSERT INTO `students` (firstname, lastname, sessid) VALUES ('%s', '%s', '%s');" % (firstname, lastname, handler.sessid())) 37 | 38 | redirect(get_index) 39 | 40 | if not db.hastable('students'): 41 | db.maketable('students', 42 | lastname='VARCHAR(1024)', 43 | firstname='VARCHAR(1024)', 44 | sessid='CHAR(16)' 45 | ) 46 | -------------------------------------------------------------------------------- /levels58/handlers/level7.py: -------------------------------------------------------------------------------- 1 | from handler import * 2 | 3 | @handler('level7/index') 4 | def get_index(error=None, username='admin', password=''): 5 | return dict(error=error, username=username, password=password) 6 | 7 | @handler 8 | def post_index(username, password): 9 | try: 10 | user = db.query("SELECT password FROM users WHERE username='%s'" % username) 11 | except Exception, e: 12 | import traceback 13 | return Response(traceback.format_exc() + '\n' + e[1], mimetype='text/plain') 14 | 15 | if len(user) == 0: 16 | redirect(get_index.url(error='User does not exist', username=username, password=password)) 17 | elif user[0][0] == password: 18 | redirect(get_success.url(username=username)) 19 | else: 20 | redirect(get_index.url(error='Invalid password', username=username, password=password)) 21 | 22 | @handler('level7/success') 23 | def get_success(username): 24 | return dict(username=username) 25 | 26 | if not db.hastable('users'): 27 | db.maketable('users', 28 | username='VARCHAR(1024)', 29 | password='VARCHAR(1024)' 30 | ) 31 | -------------------------------------------------------------------------------- /levels58/handlers/level8.py: -------------------------------------------------------------------------------- 1 | from handler import * 2 | 3 | @handler('level8/index') 4 | def get_index(): 5 | return dict(docs=db.query('SELECT id, name, mimetype FROM documents WHERE sessid=%s', handler.sessid())) 6 | 7 | @handler 8 | def post_index(name, doc): 9 | fn, mime = doc.filename, doc.mimetype 10 | 11 | doc.save('level8_sandbox/' + fn) 12 | 13 | db.query("INSERT INTO documents (name, filename, mimetype, sessid) VALUES ('%s', '%s', '%s', '%s')" % (name, fn, mime, handler.sessid())) 14 | 15 | redirect(get_index) 16 | 17 | inlinable = 'image/jpeg image/png text/plain'.split(' ') 18 | 19 | @handler 20 | def get_view(id, download='None'): 21 | download = eval(download) 22 | 23 | (filename, mimetype), = db.query('SELECT filename, mimetype FROM documents WHERE sessid=%s AND id=%s', handler.sessid(), id) 24 | 25 | if download == None and mimetype not in inlinable: 26 | download = True 27 | 28 | if download: 29 | handler.header('Content-Disposition', 'attachment; filename=' + filename) 30 | 31 | handler.header('Content-Type', mimetype) 32 | 33 | return file('level8_sandbox/' + filename, 'rb').read() 34 | 35 | if not db.hastable('documents'): 36 | db.maketable('documents', 37 | name='VARCHAR(1024)', 38 | filename='VARCHAR(1024)', 39 | mimetype='VARCHAR(1024)', 40 | sessid='CHAR(16)' 41 | ) 42 | -------------------------------------------------------------------------------- /levels58/level5_docs/42.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque pellentesque est vitae adipiscing tincidunt. Curabitur ut mattis neque. Integer sed tellus ac nisl dapibus vestibulum. Phasellus eget mattis lectus. Praesent quis bibendum nulla. Proin pulvinar dui vitae quam porttitor, non sagittis est vehicula. Duis porta risus id dictum varius. Suspendisse elementum ac nisl quis commodo. Curabitur luctus nisl quis orci tristique, a iaculis quam molestie. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed dignissim dui in elit fermentum commodo quis quis quam. Aliquam massa nibh, accumsan nec consectetur at, consectetur tincidunt orci. Vivamus euismod venenatis congue. Quisque cursus hendrerit commodo. Donec ut tempor odio. 2 | 3 | Sed pellentesque, felis sit amet bibendum pharetra, dui sem posuere ligula, at iaculis nulla risus ut lorem. Phasellus mattis tristique malesuada. Aliquam sodales odio non nulla consectetur, nec facilisis diam euismod. Aliquam pretium tincidunt magna, non iaculis sapien varius vel. Phasellus ornare leo vitae ullamcorper pulvinar. In a nisl volutpat felis luctus malesuada a eu sem. Vestibulum bibendum, purus quis vehicula ultricies, lectus urna molestie nulla, suscipit fermentum massa odio pharetra elit. Maecenas eget vestibulum nisi. 4 | 5 | Proin aliquet interdum felis, nec ultrices augue fermentum nec. Praesent est felis, pellentesque non ornare id, ultricies sit amet ante. Quisque feugiat enim non ante euismod convallis. Pellentesque tempus hendrerit massa, et tempus nibh pellentesque et. Vestibulum viverra ante libero, eu bibendum quam suscipit ac. Cras molestie sem ut turpis sodales elementum a in lorem. Aliquam erat volutpat. Proin quis vulputate eros. Quisque pretium purus quis nunc volutpat, ac rutrum turpis vestibulum. 6 | 7 | Curabitur sagittis ullamcorper massa a gravida. Phasellus non eros quis purus rhoncus sodales. Suspendisse a tellus at tortor dapibus vehicula a sit amet neque. Morbi bibendum luctus laoreet. In hac habitasse platea dictumst. Maecenas pulvinar congue hendrerit. Etiam at leo libero. 8 | 9 | Vivamus varius felis quis metus tristique, ac auctor libero lobortis. Nam vel imperdiet elit. Maecenas laoreet nisl tortor, sit amet facilisis nibh feugiat in. Nam aliquam massa ac justo tempor, sit amet semper lorem laoreet. Aliquam condimentum lacinia faucibus. Morbi scelerisque facilisis bibendum. Ut id ipsum quis augue dignissim ornare at et risus. Sed egestas neque eu tortor molestie, nec pretium velit ultrices. Praesent tincidunt leo id ligula pharetra bibendum. Ut nisi neque, malesuada a eros in, rhoncus tristique eros. Curabitur et lectus sed lorem iaculis fringilla vitae sit amet dolor. 10 | 11 | Pellentesque lacinia, urna et consequat tempus, neque magna fermentum sapien, a tempor urna arcu at sem. Pellentesque eu leo quis nisl elementum ullamcorper nec a magna. Praesent nibh nibh, sagittis ut turpis eget, vestibulum vehicula nunc. Aliquam ut convallis velit, ac placerat ante. Suspendisse ut nisi vel dolor cursus laoreet id sed erat. Proin consequat cursus sem, sed varius massa ultricies at. Nunc eu lorem quis justo cursus sodales. Donec consectetur luctus nisi ac blandit. Proin vel lorem molestie, vestibulum enim non, luctus justo. 12 | 13 | Praesent at sem ligula. Nulla condimentum nisl at neque pellentesque iaculis. Donec sed vehicula dolor. In imperdiet sapien mi, quis luctus arcu dignissim vel. Mauris id congue nisi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla et tortor bibendum, tempus nibh id, hendrerit massa. Maecenas volutpat ligula quam, non pharetra velit aliquam luctus. 14 | 15 | Nunc imperdiet tortor ut nulla mollis aliquet. Mauris lobortis luctus commodo. Nam id nisl et massa luctus mattis in id nisl. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Quisque aliquet vitae augue ut commodo. Praesent id elit sed odio faucibus consectetur. Sed quis sapien quis est hendrerit blandit posuere non urna. Praesent gravida nulla mauris, sit amet semper odio sodales vel. Vestibulum in sem aliquet amet. -------------------------------------------------------------------------------- /levels58/level8_sandbox/keep_me: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacker0x01/Hacker101Coursework/822efa707eaca9094720c26dd183e0a1d8ba63e7/levels58/level8_sandbox/keep_me -------------------------------------------------------------------------------- /levels58/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from flask import Flask, request 3 | import handler 4 | import handlers 5 | from handlers import * 6 | 7 | app = Flask(__name__) 8 | app.debug = False 9 | app.secret_key = key = 'aspdfojdpojsdapfowjfpoajpeopjafpowejcapocjpeo' 10 | 11 | def reroute(noId, withId): 12 | def sub(id=None, *args, **kwargs): 13 | try: 14 | if id == None: 15 | return noId(*args, **kwargs) 16 | else: 17 | return withId(id, *args, **kwargs) 18 | except: 19 | import traceback 20 | traceback.print_exc() 21 | sub.func_name = '__reroute_' + noId.func_name 22 | return sub 23 | 24 | for module, sub in handler.all.items(): 25 | for name, (method, args, rpc, (noId, withId)) in sub.items(): 26 | if module == 'index': 27 | route = '/' 28 | trailing = True 29 | else: 30 | route = '/%s' % module 31 | trailing = False 32 | if name != 'index': 33 | if not trailing: 34 | route += '/' 35 | route += '%s' % name 36 | trailing = False 37 | 38 | if noId != None and withId != None: 39 | func = reroute(noId, withId) 40 | elif noId != None: 41 | func = noId 42 | else: 43 | func = withId 44 | 45 | if withId != None: 46 | iroute = route 47 | if not trailing: 48 | iroute += '/' 49 | iroute += '' 50 | app.route(iroute, methods=[method])(func) 51 | 52 | if noId != None: 53 | app.route(route, methods=[method])(func) 54 | 55 | @app.route('/favicon.ico') 56 | def favicon(): 57 | return file('static/favicon.png', 'rb').read() 58 | 59 | rpcStubTemplate = '''%s: function(%s, callback) { 60 | $.ajax(%r, 61 | { 62 | success: function(data) { 63 | if(callback !== undefined) 64 | callback(data) 65 | }, 66 | error: function() { 67 | if(callback !== undefined) 68 | callback() 69 | }, 70 | dataType: 'json', 71 | data: {csrf: $csrf, %s}, 72 | type: 'POST' 73 | } 74 | ) 75 | }''' 76 | cachedRpc = None 77 | @app.route('/rpc.js') 78 | def rpc(): 79 | global cachedRpc 80 | if cachedRpc: 81 | return cachedRpc 82 | 83 | modules = [] 84 | for module, sub in handler.all.items(): 85 | module = [module] 86 | for name, (method, args, rpc, funcs) in sub.items(): 87 | if not rpc: 88 | continue 89 | func = funcs[0] if funcs[0] else funcs[1] 90 | name = name[4:] 91 | method = rpcStubTemplate % ( 92 | name, ', '.join(args), 93 | func.url(), 94 | ', '.join('%s: %s' % (arg, arg) for arg in args) 95 | ) 96 | module.append(method) 97 | if len(module) > 1: 98 | modules.append(module) 99 | 100 | cachedRpc = 'var $rpc = {%s};' % (', '.join('%s: {%s}' % (module[0], ', '.join(module[1:])) for module in modules)) 101 | return cachedRpc 102 | 103 | @app.route('/scripts/') 104 | def script(fn): 105 | try: 106 | if not fn.endswith('.js'): 107 | return '' 108 | 109 | fn = 'scripts/' + fn[:-3] 110 | if os.path.exists(fn + '.js'): 111 | return file(fn + '.js', 'rb').read() 112 | return '' 113 | except: 114 | import traceback 115 | traceback.print_exc() 116 | 117 | if __name__=='__main__': 118 | app.run(host='') 119 | -------------------------------------------------------------------------------- /levels58/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | gunicorn==19.4.5 3 | itsdangerous==0.24 4 | Jinja2==2.8 5 | MarkupSafe==0.23 6 | mysql==0.0.1 7 | MySQL-python==1.2.5 8 | Werkzeug==0.11.5 9 | -------------------------------------------------------------------------------- /levels58/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacker0x01/Hacker101Coursework/822efa707eaca9094720c26dd183e0a1d8ba63e7/levels58/static/favicon.png -------------------------------------------------------------------------------- /levels58/templates/exam1/authed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exam 1 5 | 6 | 7 | 8 |

Exam 1

9 |

Time remaining: {{ hours }} hours, {{ minutes }} minutes.

10 |

11 | I recommend that you go level-by-level, but if you find yourself getting hung up on one of the levels, skip around. Levels are unlocked by certain actions in each one, but that doesn't necessarily mean that you found all the bugs in that level, especially XSS, CSRF, and the like. 12 |

13 |

14 | Due to the nature of the vulnerabilities you're testing, it's entirely possible that you could compromise the integrity of pretty much every control that's in place here. That means that you could extend the timeframe you have for the exam, and things like that. Please don't do that! 15 |

16 |

17 | You can email your report to cody.brocious+course@gmail.com with the subject [Coursework] Exam 1. Thanks! 18 |

19 |
    20 |
  • Exam Level 0
  • 21 |
  • 22 | {% if level > 0 %} 23 | Exam Level 1 24 | {% else %} 25 | Exam Level 1 — Locked 26 | {% endif %} 27 |
  • 28 |
  • 29 | {% if level > 1 %} 30 | Exam Level 2 31 | {% else %} 32 | Exam Level 2 — Locked 33 | {% endif %} 34 |
  • 35 |
  • 36 | {% if level > 2 %} 37 | Exam Level 3 38 | {% else %} 39 | Exam Level 3 — Locked 40 | {% endif %} 41 |
  • 42 |
43 | 44 | -------------------------------------------------------------------------------- /levels58/templates/exam1/el10/admin_login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Guestbook 5 | 6 | 7 | 8 |

Breaker 101 Guestbook

9 |
$CSRF$ 10 |

Admin login

11 | Username:
12 | Password:
13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /levels58/templates/exam1/el10/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Guestbook 5 | 6 | 7 | 8 |

Breaker 101 Guestbook

9 | {% if message %} 10 |

{{ message | safe }}

11 | {% endif %} 12 |
13 |

Write in the guestbook!

14 | Name:
15 | Email:
16 |
17 | 18 |
19 | {% for id, name, email, body, date in posts %} 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | {% if 'el10_admin' in session and session['el10_admin'] %} 40 | 41 | 42 | 43 | {% endif %} 44 |
Name{{ name }}
Email{{ email }}
Date{{ date }}
36 | {{ body }} 37 |
Delete post
45 | {% endfor %} 46 |
47 | {% if 'el10_admin' not in session or session['el10_admin'] == False %} 48 | Admin login 49 | {% else %} 50 | Admin logout 51 | {% endif %} 52 | 53 | -------------------------------------------------------------------------------- /levels58/templates/exam1/el11/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Breaksys Router Administration 5 | 6 | 7 | 8 |

Breaksys Router Login

9 | {% if message %} 10 |

{{ message }}

11 | {% endif %} 12 | {% if not admin %} 13 |
$CSRF$ 14 |

Log In

15 | Username:
16 | Password:
17 | 18 |
19 | {% else %} 20 |

Logged in as {{ settings['admin_username'] | safe }}. Log out

21 | {% endif %} 22 |

Status

23 | 24 | 35 | 47 |
SSID 25 | {% if admin %} 26 |
27 | 28 | 29 | 30 |
31 | {% else %} 32 | {{ settings['ssid'] }} 33 | {% endif %} 34 |
Router IP 36 | {% if admin %} 37 |
38 | 39 | 40 | 41 |
42 | {% else %} 43 | {{ settings['int_ip'] }} 44 | {% endif %} 45 | 46 |
48 | 49 | {% if admin %} 50 |

Diagnostics

51 |
52 | 53 | 54 | 55 | Ping:
56 |
57 |
58 | 59 | Traceroute:
60 |
61 | 62 | {% endif %} 63 | 64 | -------------------------------------------------------------------------------- /levels58/templates/exam1/el12/create.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | New Page — Breaker Wiki 5 | 6 | 7 | 8 |

Create Page

9 |
$CSRF$ 10 | Name:
11 |
12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /levels58/templates/exam1/el12/edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Editing {{ page }} — Breaker Wiki 5 | 6 | 7 | 8 |

Edit {{ page }}

9 |
$CSRF$ 10 | 11 |
12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /levels58/templates/exam1/el12/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page }} — Breaker Wiki 5 | 6 | 7 | 8 | Home 9 |

{{ page }}

10 | {% if not found %} 11 | Page does not exist. Create it 12 | {% else %} 13 | {{ body | safe }} 14 |
15 |
16 | Edit this page or create a new page 17 | {% endif %} 18 | 19 | -------------------------------------------------------------------------------- /levels58/templates/exam1/el13/feedback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Breaker Feedback 5 | 6 | 7 | 8 |

Breaker Feedback

9 | 10 | {% for name, message in messages %} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
Name{{ name }}
{{ message.replace('\n', '
') | safe }}
20 |
21 | {% endfor %} 22 | 23 | -------------------------------------------------------------------------------- /levels58/templates/exam1/el13/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Breaker Feedback 5 | 6 | 7 | 8 |

Breaker Feedback

9 | {% if message %} 10 |

{{ message | safe }}

11 | {% endif %} 12 | 13 | Leave feedback for the admins below:
14 |
$CSRF$ 15 | Your name:
16 |
17 | 18 |
19 |
20 | Log in to read submitted feedback 21 | 22 | -------------------------------------------------------------------------------- /levels58/templates/exam1/el13/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Breaker Feedback 5 | 6 | 7 | 8 |

Breaker Feedback

9 |
$CSRF$ 10 |

Admin login

11 | Username:
12 | Password:
13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /levels58/templates/exam1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exam 1 5 | 6 | 7 | 8 |

Exam 1

9 |

Welcome to the first exam. Create a user or login to continue. The user system is not known to be buggy (not that I expect that will deter any of you from trying to break it!)

10 |

Note well: Once you create an account, you only have 4 hours of testing before you get locked out of the system! Make your time count, and make sure that you do all the coursework before attempting the exam.

11 | {% if error != None %} 12 | {{ error }} 13 | {% endif %} 14 |

Create user

15 |
$CSRF$ 16 | Username:
17 | Password:
18 | Confirm password:
19 |

Note: Your password is being transmitted in cleartext, and I'm storing it that way as well. You should NOT be using a real password here.

20 | 21 |
22 |

Log In

23 |
$CSRF$ 24 | Username:
25 | Password:
26 | 27 |
28 | 29 | -------------------------------------------------------------------------------- /levels58/templates/level5/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Document Repository 5 | 6 | 14 | 15 | 16 |

Document Repository -- {{ path }}

17 |
18 |
    19 | {% for dir in dirs %} 20 |
  • {{ dir }}/
  • 21 | {% endfor %} 22 | {% for file in files %} 23 |
  • {{ file }}
  • 24 | {% endfor %} 25 |
26 |
27 |
$CSRF$ 28 | 29 | Search in directory:
30 | 31 |
32 | 33 | -------------------------------------------------------------------------------- /levels58/templates/level6/add.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Student Center 5 | 6 | 7 | 8 |

Add Student

9 |
10 |
$CSRF$ 11 | First name:
12 | Last name:
13 |
14 | 15 |
16 | 17 | -------------------------------------------------------------------------------- /levels58/templates/level6/edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Student Center 5 | 6 | 7 | 8 |

Edit student {{ student[1] }}, {{ student[2] }}

9 |
10 |
$CSRF$ 11 | First name:
12 | Last name:
13 |
14 | 15 |
16 | 17 | -------------------------------------------------------------------------------- /levels58/templates/level6/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Student Center 5 | 6 | 16 | 17 | 18 |

Student Center

19 | Add Student 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {% for student in students %} 29 | 30 | 31 | 32 | 33 | 35 | {% endfor %} 36 |
IDLast NameFirst Name 
{{ student[0] | safe }}{{ student[1] }}{{ student[2] }}Edit 34 |
37 |
38 | Filter: 39 | 40 |
41 | 42 | -------------------------------------------------------------------------------- /levels58/templates/level7/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Guardian 5 | 6 | 13 | 14 | 15 |

Guardian

16 |
$CSRF$ 17 | Username:
18 | Password:
19 | 20 |

21 |
22 | {% if error is not none %} 23 | {{ error }} 24 | {% endif %} 25 | 26 | -------------------------------------------------------------------------------- /levels58/templates/level7/success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Guardian 5 | 6 | 7 | 8 |

Guardian

9 | Successfully logged in as user {{ username }}! Congrats on beating level7. 10 | 11 | -------------------------------------------------------------------------------- /levels58/templates/level8/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Document Exchange 5 | 6 | 17 | 18 | 19 |

Document Exchange

20 |
$CSRF$ 21 |

Upload

22 | Name:
23 |
24 | 25 |
26 | 27 | 28 | 29 | 30 | {% for (id, name, mimetype) in docs %} 31 | 32 | 33 | 34 | 35 | 36 | 37 | {% endfor %} 38 |
NameMIME Type
{{ name }}{{ mimetype | safe }}ViewDownload
39 | 40 | --------------------------------------------------------------------------------