Security issues documented, and more nice improvements

This commit is contained in:
Sivert V. Sæther 2022-06-10 15:35:31 +00:00
parent f3668df846
commit 93934438c7
21 changed files with 178 additions and 43 deletions

View File

@ -36,5 +36,7 @@ debug:
$(DEV) --debug $(DEV) --debug
clean: clean_pub clean: clean_pub
rm -fr resources/ hugo_stats.json .hugo_build.lock rm -fr resources/ hugo_stats.json .hugo_build.lock
clean_js: clean
rm -fr node_modules/ yarn.lock
clean_pub: clean_pub:
rm -fr public/ rm -fr public/

View File

@ -46,7 +46,6 @@ Handlebars.registerPartial('people', `
{{#each members}} {{#each members}}
<li>{{name}} age {{age}} from {{home}}</li> <li>{{name}} age {{age}} from {{home}}</li>
{{/each}} {{/each}}
balle!
</ul>`) </ul>`)
// Calling non-existent partials with block syntax uses the block content as fallback // Calling non-existent partials with block syntax uses the block content as fallback

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,6 +1,6 @@
.chroma { .chroma {
padding: .1vh 0 0; margin: 0 0 .7vh; min-height: 3.3vh; min-height: 3.3vh; overflow: auto;
color: #ffffff; background-color: #111111 } color: #ffffff; background-color: #111111; }
.chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0 } .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0 }
.chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0 } .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0 }
.chroma .hl { background-color: #ffffcc } .chroma .hl { background-color: #ffffcc }

View File

@ -26,7 +26,7 @@ figure {
margin: 3% 10%; margin: 3% 10%;
padding: 3%; padding: 3%;
article { article {
max-width: 85%; max-width: 90%;
} }
} }
a:hover %headings { a:hover %headings {

View File

@ -5,7 +5,7 @@ aliases: []
categories: ['meta'] categories: ['meta']
series: [] series: []
tags: ['info', 'meta'] tags: ['info', 'meta']
toc: false toc: true
title: Welcome! title: Welcome!
description: Landing page for Siverts GitHub Pages blog and portfolio. description: Landing page for Siverts GitHub Pages blog and portfolio.
--- ---

View File

@ -5,7 +5,7 @@ aliases: []
categories: ['meta'] categories: ['meta']
series: [] series: []
tags: ['info','meta'] tags: ['info','meta']
toc: false toc: true
title: Siverts Tech Adventure Blog title: Siverts Tech Adventure Blog
description: Landing page for Siverts Tech Adventure Blog! description: Landing page for Siverts Tech Adventure Blog!
--- ---

View File

@ -5,7 +5,7 @@ aliases: []
categories: ['meta'] categories: ['meta']
series: [] series: []
tags: ['info','meta'] tags: ['info','meta']
toc: false toc: true
title: Siverts Technisch Avontuur Blog title: Siverts Technisch Avontuur Blog
description: Bestemmingspagina voor Siverts Siverts Technisch Avontuur Blog! description: Bestemmingspagina voor Siverts Siverts Technisch Avontuur Blog!
--- ---

View File

@ -5,7 +5,7 @@ aliases: []
categories: ['meta'] categories: ['meta']
series: [] series: []
tags: ['info','meta'] tags: ['info','meta']
toc: false toc: true
title: Siverts Teknologi Evenetyr Blog title: Siverts Teknologi Evenetyr Blog
description: Landingsside for Siverts Teknologi Evenetyr Blog! description: Landingsside for Siverts Teknologi Evenetyr Blog!
--- ---

View File

@ -5,7 +5,7 @@ aliases: []
categories: ['apprentice', 'indices', 'meta'] categories: ['apprentice', 'indices', 'meta']
series: ['apprentice'] series: ['apprentice']
tags: ['apprentice'] tags: ['apprentice']
toc: false toc: true
title: IT Apprentice title: IT Apprentice
description: This part of my blog is a collection of things I've learned as an apprentice working in IT. description: This part of my blog is a collection of things I've learned as an apprentice working in IT.
--- ---
@ -16,29 +16,30 @@ It also serves the purpose of the required documentation Apprentices in Norway a
Here are lists of stuff I'll be writing about here; Here are lists of stuff I'll be writing about here;
### Programming ## Programming
#### Python ### Python
- Flask :baby_bottle: - Flask :baby_bottle:
- SQLAlchemy :sake: - SQLAlchemy :sake:
#### Browser JavaScript - MSAL (Microsoft Authentication Library) :banjo:
### Browser JavaScript
- jQuery :calling: - jQuery :calling:
- Handlebars.js :wavy_dash: - Handlebars.js :wavy_dash:
### Programs ## Programs / Tools
- FreeRADIUS :crystal_ball: - FreeRADIUS :crystal_ball:
- Postgres :floppy_disk: - Postgres :floppy_disk:
- Ansible :gun: - Ansible :gun:
### Operating Systems/Networking devices ## OS / Networking
- Debian :dolls: - Debian :dolls:
- Mikrotik :package: - Mikrotik :package:
- Cisco Meraki :cloud: - Cisco Meraki :cloud:
### Azure ## Azure
- App Registration :rocket: - App Registration :rocket:
- Active Directory :dizzy: - Active Directory :dizzy:
### The WHM saga (shortie) ## The WHM saga (shortie)
- Wordpress :eyes: - Wordpress :eyes:
- cPanel :shit: - cPanel :shit:
- WHM :ok_hand: - WHM :ok_hand:

View File

@ -67,3 +67,6 @@ If it's a tenant admin that's logging in that person may click the check for gra
Small and simple powershell script for adding the "service principal" to a tenant; Small and simple powershell script for adding the "service principal" to a tenant;
{{< highlight powershell >}}{{% asset "/apprentice/azure/service-principal.ps1" %}}{{< /highlight >}} {{< highlight powershell >}}{{% asset "/apprentice/azure/service-principal.ps1" %}}{{< /highlight >}}
After the service principal is created in the Azure AD tenant with users that's going to consume the app.
Admins may set "App Roles" per user under "Enterprise Applications" in Azure.

View File

@ -2,9 +2,9 @@
date: 2022-05-25T09:54:01Z date: 2022-05-25T09:54:01Z
draft: true draft: true
aliases: [] aliases: []
categories: ['various'] categories: ['apprentice', 'expose']
series: [] series: ['apprentice', 'expose']
tags: ['various'] tags: ['apprentice', 'expose', 'various']
toc: true toc: true
title: Sircon title: Sircon
description: description:

View File

@ -0,0 +1,12 @@
---
date: 2022-06-08T10:34:42Z
draft: true
aliases: []
categories: ['various']
series: []
tags: ['various']
chroma: false
toc: true
title: Mikrotik
description:
---

View File

@ -1,14 +1,119 @@
--- ---
date: 2022-06-07T07:15:51Z date: 2022-06-07T07:15:51Z
draft: true draft: false
aliases: [] aliases: []
categories: ['various'] categories: ['apprentice']
series: [] series: ['hacking', 'security']
tags: ['various'] tags: ['apprentice', 'tech', 'bug']
chroma: true
toc: true toc: true
title: Security title: Security issues
description: description: Various security issues I found, disclosed and got fixed as an apprentice!
--- ---
Apache .htconfig fuckery, WP bug. Here is a collection of security issues I found, disclosed and got fixed as an apprentice!
Ansible and intrauser ssh keys. At SkyLabs I fixed the issues myself, but at Sircon they delegated all the boring support to me and the previous apprentice.
So there I just disclosed the issue to the responsible person and both of them fixed their respective issues within an hour.
## SkyLabs
SkyLabs has surprisingly good stability compared to expectations based on the codebase.
I'm guessing it's a byproduct of Python that let's us fail pretty safely on almost all endpoints.
The in-house development is mainly four services, two JS frontends and two Python Flask backends, all web.
These services are a captive portal, it's API and an admin interface web app and it's API.
We support lots of interesting [authentication](https://wiki.skylabs.no/partner:authentication) methods!~
### Sky ID Admin
And so far the only real security related issue I've found in the in-house codebase is that the Access Control was missing a check.
The problem is that API keys that are associated with a deleted account is valid.
Allowed methods are by x-api-key Authorization header, api-key as an url parameter or by Json Web Token.
The JWT is passed in an Authorization Bearer header, which is wrong, [Bearer](https://datatracker.ietf.org/doc/html/rfc6750) Auth header prefix should be reserved for [OAuth 2](https://datatracker.ietf.org/doc/html/rfc6749).
Anyway when using API keys, not JWT, the server just fetches the API key record from the database.
The function calls for this goes from the Access Control controller class through an API key controller.
Problem is the API key controller class only checks if the associated user exists, not if it's been marked as deleted.
So if you have an API key, you may have your accounts full access after it's supposedly been deleted.
And the fix for it was super easy!~
We use a custom endpoint decorator, like flasks own @app.route, but we control what happens.
It's here the authentication is implemented, on all requests that go to routes defined with this decorator will run the Access Control controller.
Access Control goes through the auth options in it's dunder init function and sets self.current_user to some admin user from the database.
To fix the problem I could've made this check specific to the API keys by putting it in the API key controller that also has a user getter function.
But I chose to add an if statement to the Access Control user getter, this is called by our endpoint decorator.
So I literally just changed this.
{{< highlight py >}}
class AccessControl:
current_user = None
...
def get_current_user(self):
return self.current_user
{{< /highlight >}}
To that!
{{< highlight py >}}
class AccessControl:
current_user = None
...
def get_current_user(self):
if self.current_user.deleted is None:
return self.current_user
else:
raise Forbidden
{{< /highlight >}}
To fix this issue.
### Servers
On the other side, the cloud servers had some bigger problems.
Here I did find a *BIG* security issue. Ancient ssh keys.
We use Ansible to manage the servers and deploy code updates.
There is an Ansible var file that has a list of user accounts with options for shell and ssh pubkey.
That file also has a list of old accounts that should be deleted.
These are per employee users.
But the intrauser and ansible users have existed since 2016...
using the same keys that they were created with.
The reason this is such a big threat is the fact that intrauser, ansible and all employee accounts has sudo access without password.
So check it; these screenshots are from SkyLabs' Ansible git log!~
{{< img src="apprentice/skyid/old-intrauser-key.png" caption="The old intrauser ssh key" >}}
{{< img src="apprentice/skyid/intrauser-key.png" caption="Me finally updating the key" >}}
## Sircon
These guys has a few beefy ass physical servers in their own rack in a supposedly EMP and fire safe room in the basement of their offices.
Those physical machines run virtual machines that run WHM/cPanel and whatever PHP app the customer would like.
But >90% of their customers are non-technical and just only interact with the default WordPress setup and maybe the e-mail service that comes with their cPanel.
Most of the people calling in to their support line has problems using their e-mail, and it's usually the end user who fucked up.
### Servers
Have you ever used Apache httpd? Have you ever used its .htaccess config?
Well it's pretty cool, but may pose a threat on shared "web hotels" like WHM servers running a whole lot of cPanel accounts.
If using .htaccess is allowed users may set their own configuration options for httpd.
And the issue I identified here was the fact that cgi configuration options where allowed in .htaccess.
So I had some fun making wired cgi scripts in python, perl and bash.
The last thing I did before disclosing it to Sircons server guy was to make a proof of concept script that gave me a shell on the server.
As they don't allow ssh access and PHP is configured to not allow anything dangerous, like the system() function, that would allow you to create shells.
Example .htaccess to run cgi scripts.
{{< highlight apache >}}
Options +ExecCGI
AddHandler cgi-script .cgi .pl .py .sh .rb
{{< /highlight >}}
The fix for this is the [AllowOverride](https://httpd.apache.org/docs/current/mod/core.html#allowoverride) directive.
Just make sure it does not include "FileInfo".
### WordPress
I found a bug on their homepage [sircon.no](https://sircon.no) where they have a simple WordPress shop with a couple of cool features, like [this](https://sircon.no/sjekk-om-din-nettbutikk-nettsted-driftes-miljovennlig/) where you supposedly can check how green a web page is. As in green energy.
The bug I found was missing server side validation and a shopping cart that's stored in the browsers local storage with price specified.
This is a pretty minor issue however, as they don't do automatic provisioning, and it only gets added to some internal webpage they use to keep track of everything.
But whatever you posted as the price would show up in that tool.
You could even post negative prices!

View File

@ -1,7 +1,7 @@
{{ define "main" }} {{ define "main" }}
<article> <article>
<header> <header>
<h1>{{ .Title | humanize }}</h1> <h1>{{ .Title }}</h1>
</header> </header>
{{ .Content }} {{ .Content }}
{{if gt (len .Pages) 0}} {{if gt (len .Pages) 0}}
@ -10,9 +10,7 @@
<ul class='contents'> <ul class='contents'>
{{ range .Pages }} {{ range .Pages }}
<br /> <br />
<a href='{{ .RelPermalink }}'> <a href='{{ .RelPermalink }}'><li>{{ partial "summary.html" . }}</li></a>
<li>{{ partial "summary.html" . }}</li>
</a>
{{ end }} {{ end }}
</ul> </ul>
{{ end }} {{ end }}

View File

@ -1,7 +1,7 @@
{{ define "main" }} {{ define "main" }}
<article> <article>
<header> <header>
<h1>{{ .Title | humanize }}</h1> <h1>{{ .Title }}</h1>
</header> </header>
{{ .Content }} {{ .Content }}
</article> </article>

View File

@ -1,4 +1,4 @@
{{ if .IsTranslated }}<h4>{{ T "translations" }}</h4> {{ if .IsTranslated }}<h6>{{ T "translations" }}:</h6>
<ul>{{ range .Translations }} <ul>{{ range .Translations }}
<li> <li>
<a href='{{ .RelPermalink }}'>{{ .Lang }}: {{ .Title }}{{ if .IsPage }} ({{ i18n "wordCount" . }}){{ end }}</a> <a href='{{ .RelPermalink }}'>{{ .Lang }}: {{ .Title }}{{ if .IsPage }} ({{ i18n "wordCount" . }}){{ end }}</a>

View File

@ -2,8 +2,7 @@
{{- $lastmod := .Lastmod.Format "02/01/2006" }} {{- $lastmod := .Lastmod.Format "02/01/2006" }}
<article> <article>
<div> <div>
<!-- <h3><a href='{{ .RelPermalink }}'>{{ .Title }}</a></h3> --> <h3>{{ .Title }}</h3>
<h3>{{ .Title | humanize }}</h3>
{{ i18n "published" }} {{ $pubdate }} {{ i18n "published" }} {{ $pubdate }}
{{ if ne $pubdate $lastmod }} {{ if ne $pubdate $lastmod }}
{{ i18n "modified" }} {{ $lastmod }} {{ i18n "modified" }} {{ $lastmod }}

View File

@ -1,13 +1,13 @@
<aside id='meta'> <aside id='meta'>
<header> <header>
<h4>{{ or (.Param "tit") .Title | humanize }}</h4> <h4>{{ or (.Param "tit") .Title }}</h4>
<p> <p>
{{ i18n "readingTime" .ReadingTime }}.
{{ i18n "wordCount" .WordCount }}. {{ i18n "wordCount" .WordCount }}.
{{ i18n "readingTime" .ReadingTime }}.
</p> </p>
</header> </header>
{{ if .Param "docs" }} {{ if .Param "docs" }}
<h6>Documentation:</h6> <h5>Documentation:</h5>
<ul> <ul>
{{ range .Param "docs" }} {{ range .Param "docs" }}
<li><a href='{{ .url }}' target='_blank'>{{ .name }}</a></li> <li><a href='{{ .url }}' target='_blank'>{{ .name }}</a></li>
@ -15,7 +15,18 @@
{{ with .Param "git" }} {{ with .Param "git" }}
<p>Check out the git repo; <a href='{{ .url }}'>{{ .name }}</a>!</p> <p>Check out the git repo; <a href='{{ .url }}'>{{ .name }}</a>!</p>
{{ end }} {{ end }}
{{ $toc := .TableOfContents }}
{{ if ne $toc "<nav id=\"TableOfContents\"></nav>" }}
<h6>Table of Contents:</h6> <h6>Table of Contents:</h6>
{{ .TableOfContents }} {{ $toc }}
{{ end }}
{{ partial "i18n/list.html" . }} {{ partial "i18n/list.html" . }}
{{ if and (ne (len .Pages) 0) $.IsNode }}
<h5>Posts:</h5>
<ul>
{{ range .Pages }}
<li><a href='{{ .RelPermalink }}' target='_top'>{{ .Title }}</a></li>
{{ end }}
</ul>
{{ end }}
</aside> </aside>

View File

@ -2,4 +2,9 @@
{{ if .Get 1 }} {{ if .Get 1 }}
{{ $opts = $opts | append (split (.Get 1) ",") }} {{ $opts = $opts | append (split (.Get 1) ",") }}
{{ end }} {{ end }}
{{ transform.Highlight (htmlUnescape .Inner) (.Get 0) (delimit $opts ",") }} {{ $content := .Inner }}
{{ $in := split .Inner "\n" }}
{{ if gt (len $in) 1 }}
{{ $content = (delimit (after 1 $in) "\n") }}
{{ end }}
{{ transform.Highlight (htmlUnescape $content) (.Get 0) (delimit $opts ",") }}