Linux servers - Exercice 3 ============== *Disclaimer:* -------------- This exercise is a part of [Linux servers (ICT4TN021, spring 2018) // Linux-palvelimet (ICT4TN021, kevät 2018)](http://www.haaga-helia.fi/fi/opinto-opas/opintojaksokuvaukset/ICT4TN021) school course organized as a part of Information Technology studies in Haaga-Helia university of Applied Sciences, Helsinki, Finland. Course lecturer [Tero Karvinen](http://terokarvinen.com/) has defined the original assignment descriptions in Finnish presented in this document in English. Answers and translations have been written by Pekka Helenius (me, ~ Fincer). **a)** Install Apache web server and set up a working web environment for each system user (http://example.com/~tero). Test the correct functionality with a sample website. -------------- **Answer:** ``` #!/bin/sh ############ # List package names of Uncomplicated Firewall ja Apache HTTP web server in a new variable PROGRAMS # PROGRAMS="ufw apache2" ############ # Define new function 'install' # # Function 'install' - BEGIN function install() { ############ # Look for executable 'apt-get', silent error printing (stderr), print count of matching strings. # Define these checks in a new variable APT_CHECK so that these checks can easily be performed later in the script. # # grep part could have been excluded but it is still good to check that this file is not compromised and is truly # an executable binary file. # # EXAMPLE If the system is compromised and the attacker replaces file /usr/bin/apt-get with his/her own version, the following # APT_CHECK might successfully pass without additional grep pipe (check additional notes below). # # Demonstration is as follows (in root shell, because we assume that the attacker has access to root account). # It is pretty clear that if the attacker has root access, almost all hope is lost because the attacker can likely access and modify almost any part in the system. However, we don't speculate with this option here. # # root@my-machine:/home/phelenius# echo -e "#\!/bin/sh\necho \"Attacker's code\"\n" > /usr/bin/malicious # root@my-machine:/home/phelenius# chmod +x /usr/bin/malicious # root@my-machine:/home/phelenius# which malicious # /usr/bin/malicious # # The file /usr/bin/malicious is identified as executable. However, if we check its mimetype, we can find out the following: # root@my-machine:/home/phelenius# file --mime-type $(which malicious) # /usr/bin/malicious: text/plain # # Therefore we can find out the executable we though would be a binary is simply a text file. In similar way, the following APT_CHECK command would pass without grep pipe which fills the security gap a little bit. However, there's still a hole. A major risk still exist because attacker could have replaced the valid apt-get executable with his/her own malicious binary file (with correct mimetype application/x-sharedlib). In this scenario, a checksum validation should seriously be considered (compare against a trusted apt-get binary file). # APT_CHECK=$(file --mime-type $(which apt-get) 2> /dev/null | grep -o application | wc -w) ############ # The next if statement has the following conditions: # if apt-get command is found in the system (APT_CHECK variable check), and... # ...if the current user is not root (UID is not 0), and... # ...the current user belongs to sudo group # if [[ $APT_CHECK -eq 1 ]] && [[ $(id -u) -ne 0 ]] && [[ $(groups | grep -o sudo | wc -w) -eq 1 ]]; then ############ # Update packages from sources listed in /etc/apt/sources.list file and in /etc/apt/sources.d/ directory # sudo apt-get update ############ # Install programs, which have been defined in the variable PROGRAMS # sudo apt-get -y install $PROGRAMS ############ # If the previous command succeeded, pass and execute the commands inside if statement # if [[ $? -eq 0 ]]; then ############ # Enable Apache2 specific userdir module # sudo a2enmod userdir ############ # Enable "ServerName" parameter with value 'www.example.com' in Apache's default page configuration file (000-default.conf) # sudo sed -i 's/#ServerName www\.example\.com/ServerName www\.example\.com/' /etc/apache2/sites-enabled/000-default.conf ############ # Add "ServerAlias" after "ServerName" in Apache's default page configuration file (000-default.conf) # sudo sed -i '/ServerName www\.example\.com/a\ \ \ \ \ \ \ \ ServerAlias example\.com' /etc/apache2/sites-enabled/000-default.conf ############ # Add new local virtual host example.com to /etc/hosts file # echo -e "127.0.0.1\texample.com" | sudo tee -a /etc/hosts ############ # Restart Apache2 HTTP web server daemon # sudo systemctl restart apache2.service fi fi } # Function 'install' - END ############ # Execute the function 'install', described above # install ############ # Create public_html directory in the current user's home directory (either $HOME or ~ can be used) # mkdir -p ~/public_html/ ############ # Create a new file index.html inside $HOME/public_html with the following html contents: # echo -e '\ \n \ \n \ \t\n \ \t\tTest\n \ \t\n \ \t\n \ \t\t

Test

\n \ \t\n \ \n \ ' \ > ~/public_html/index.html # About the echo command: # \t stands for tabulator # \n stands for a newline # \ stands for a newline for multiline input in shell environment # > stands for writing the contents of the echo command into the file/output mentioned after this symbol. # -e stands for "enable interpretation of backslash escapes" (meaning that you can use \t \n \r \s....symbols in echo command) ################################# # Is the following procedure secure? However, something found in the internet: # # "Basically, the Apache server does not only require read permissions of all files it serves, but the execution permission of all directories in the path of your virtual host." # https://askubuntu.com/questions/451922/apache-access-denied-because-search-permissions-are-missing # chmod 701 $HOME ############ # Test the created site with the default graphical web browser as the current user (retrieved with whoami command + bash internal command substitution method) # xdg-open http://example.com/~$(whoami)/ ``` **b)** Browse and explore the websites on your local host. Find out any hints of successful (200 ok) website load events and failed load events (for example, 404 not found). Analyse the relevant lines. -------------- **Answer:** **NOTE!** My detailed answer to this assignment is in the previous exercise 2, section a) (successful & failed event + analysing them). Link: [Exercise 2](https://github.com/Fincer/linux_server_setup/blob/master/exercises/h2.md) In my answer I have analysed Apache web server log lines. The logic is exactly same than required in this assignment. Apache logs each event when a client (web browser, for example) try to access a file or location in server. **c)** Create a purposeful error in any code line which is executed by Apache web server (PHP or Python, for example). Find out any relevant log lines and analyse them in detail. -------------- **Answer:** Let's install PHP 7.0 with Apache2, and execute a shell script which does as required. ``` #!/bin/sh ############ # Look for executable 'apt-get', silent error printing (stderr), print count of matching strings. # Define these checks in a new variable APT_CHECK so that these checks can easily be performed later in the script. APT_CHECK=$(file --mime-type $(which apt-get) 2> /dev/null | grep -o application | wc -w) ############ # Look for executable 'wget', silent error printing (stderr), print count of matching strings. # Define these checks in a new variable WGET_CHECK so that these checks can easily be performed later in the script. WGET_CHECK=$(file --mime-type $(which wget) 2> /dev/null | grep -o application | wc -w) ############ # Print count of matching strings which have retrieved by checking any packages containing 'apache2' on Debian system. # Define this check in a new variable APACHE2_CHECK so that this check can easily be performed later in the script. APACHE2_CHECK=$(dpkg --get-selections | grep apache2 | wc -l) ############ # External PHP code download link (NOTE! Downloading untrusted files is not recommended in security oriented system environments!) # Define this command in a new variable SAMPLE_CODE so that it can easily be called later in the script. SAMPLE_CODE="https://gist.githubusercontent.com/pchatterjee/3756368/raw/40c241c344c3e8d2333cc0c496e2782d9a1e6d93/calculator_v2.php" ############ # The next if statement has the following conditions: # if apt-get command is found in the system (APT_CHECK variable check), and... # ...if the current user is not root (UID is not 0), and... # ...the current user belongs to sudo group # if [[ $APT_CHECK -eq 1 ]] && [[ $(id -u) -ne 0 ]] && [[ $(groups | grep -o sudo | wc -w) -eq 1 ]]; then ############ # If apache2 is installed in the system, then... # if [[ $APACHE2_CHECK -gt 0 ]]; then ############ # install PHP 7.0 and relevant Apache2 PHP 7.0 modules # sudo apt-get install -y php7.0 libapache2-mod-php ############ # Comment the following lines in file /etc/apache2/modules-available/php7.0.conf, so that PHP works for with userdir module. # # TODO Better method should be coded. The current method has a risk that it replaces wrong lines, especially if there are multiple similar lines in a file. # Better approach would be to replace lines by starting from line which includes pattern ?#?' /etc/apache2/mods-enabled/php7.0.conf sed -i 's??#?' /etc/apache2/mods-enabled/php7.0.conf sed -i 's?php_admin_flag engine Off?#php_admin_flag engine Off?' /etc/apache2/mods-enabled/php7.0.conf sed -i 's??#?' /etc/apache2/mods-enabled/php7.0.conf sed -i 's??#?' /etc/apache2/mods-enabled/php7.0.conf ############ # If command wget is available on the system, then... # if [[ $WGET_CHECK -eq 1 ]]; then ############ # Download sample PHP code into directory $HOME/public_html/ # NOTE! Can we trust the downloaded code? # echo -e "\nDownloading sample PHP code for testing purposes (requires internet connection, security risk exists! Use local trusted code if system hardening means anything to you...)\n" # wget -P $HOME/public_html/ $SAMPLE_CODE ############ # Let's generate a purposeful error in file $HOME/public_html/calculator_v2.php by altering the code with sed command (switch -> switchasd) # sed -i 's/switch($op) {/switchasd($op) {/' $HOME/public_html/calculator_v2.php ############ # Let's try to open the erroneous PHP site with the default graphical web browser. # xdg-open command refers to default program defined to open specific mimetypes or protocols in Linux operating system. # xdg-open http://example.com/~$(whoami)/calculator_v2.php ############ # Command 'wget' is not found in the system # else echo -e "\nInstall application 'wget' before downloading a sample code" fi ############ # Packages matching string 'apache2' can't be found in the system by doing the check via Debian package manager # else echo -e "\nYou need to install Apache web server before PHP 7.0\n" exit fi fi ``` Apache web server prints out the following error message in _/var/log/apache2/error.log_ while trying to open local website _example.com/~phelenius/calculator_v2.php_: ``` [Wed Feb 07 00:20:02.923994 2018] [:error] [pid 14260] [client 127.0.0.1:38430] PHP Parse error: syntax error, unexpected 'case' (T_CASE) in /home/phelenius/public_html/calculator_v2.php on line 20 [Wed Feb 07 00:20:03.203824 2018] [:error] [pid 14259] [client 127.0.0.1:38432] PHP Parse error: syntax error, unexpected 'case' (T_CASE) in /home/phelenius/public_html/calculator_v2.php on line 20 [Wed Feb 07 00:20:03.684123 2018] [:error] [pid 14258] [client 127.0.0.1:38434] PHP Parse error: syntax error, unexpected 'case' (T_CASE) in /home/phelenius/public_html/calculator_v2.php on line 20 [Wed Feb 07 00:20:03.932480 2018] [:error] [pid 14268] [client 127.0.0.1:38436] PHP Parse error: syntax error, unexpected 'case' (T_CASE) in /home/phelenius/public_html/calculator_v2.php on line 20 [Wed Feb 07 00:20:04.269771 2018] [:error] [pid 14261] [client 127.0.0.1:38438] PHP Parse error: syntax error, unexpected 'case' (T_CASE) in /home/phelenius/public_html/calculator_v2.php on line 20 [Wed Feb 07 00:20:04.470184 2018] [:error] [pid 14262] [client 127.0.0.1:38440] PHP Parse error: syntax error, unexpected 'case' (T_CASE) in /home/phelenius/public_html/calculator_v2.php on line 20 ``` Apache web server complains multiple times about a PHP syntax error in /home/phelenius/public_html/calculator_v2.php file, on line 20. Mentioned PIDs (Process IDs) belong to apache2 process: ``` phelenius@my-machine:~$ ps aux |grep -E "14261|14262|14268|14258|14259|14260" www-data 14258 0.0 0.5 253844 11648 ? S 00:16 0:00 /usr/sbin/apache2 -k start www-data 14259 0.0 0.5 253844 11648 ? S 00:16 0:00 /usr/sbin/apache2 -k start www-data 14260 0.0 0.5 253844 11648 ? S 00:16 0:00 /usr/sbin/apache2 -k start www-data 14261 0.0 0.7 254136 14372 ? S 00:16 0:00 /usr/sbin/apache2 -k start www-data 14262 0.0 0.5 253844 11648 ? S 00:16 0:00 /usr/sbin/apache2 -k start www-data 14268 0.0 0.5 253844 11648 ? S 00:16 0:00 /usr/sbin/apache2 -k start ``` IP address 127.0.0.1 refers to local host, numbers 38430, 38432, 38434, 38436, 38438 and 38440 to the ports where the connection has been established from. The equivalent log events in _/var/log/apache2/access.log_: ``` 127.0.0.1 - - [07/Feb/2018:00:20:02 +0200] "GET /~phelenius/calculator_v2.php HTTP/1.1" 500 185 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3300.0 Iron Safari/537.36" 127.0.0.1 - - [07/Feb/2018:00:20:03 +0200] "GET /~phelenius/calculator_v2.php HTTP/1.1" 500 185 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3300.0 Iron Safari/537.36" 127.0.0.1 - - [07/Feb/2018:00:20:03 +0200] "GET /~phelenius/calculator_v2.php HTTP/1.1" 500 185 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3300.0 Iron Safari/537.36" 127.0.0.1 - - [07/Feb/2018:00:20:03 +0200] "GET /~phelenius/calculator_v2.php HTTP/1.1" 500 185 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3300.0 Iron Safari/537.36" 127.0.0.1 - - [07/Feb/2018:00:20:04 +0200] "GET /~phelenius/calculator_v2.php HTTP/1.1" 500 185 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3300.0 Iron Safari/537.36" 127.0.0.1 - - [07/Feb/2018:00:20:04 +0200] "GET /~phelenius/calculator_v2.php HTTP/1.1" 500 185 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3300.0 Iron Safari/537.36" ``` - User's client program (address 127.0.0.1/localhost + client is web browser in this case) tries to retrieve server website _/~phelenius/calculator_v2.php_ for which the Apache web server has responded with error code 500 ([HTTP_INTERNAL_SERVER_ERROR](https://ci.apache.org/projects/httpd/trunk/doxygen/group__HTTP__Status.html#ga5d9777e02c26063c2985e39ef71091d2)). - User ID is simply a line symbol - Log time is "07/Feb/2018:00:20:04 +0200" etc - HTTP method used is [GET](https://www.w3schools.com/tags/ref_httpmethods.asp) - has [HTTP Referer](https://en.wikipedia.org/wiki/HTTP_referer) - size of the object is 185 (reported to the client program) - [User agent header](https://en.wikipedia.org/wiki/User_agent) reported by the client. According to the agent string, client browser has been gecko-based [Epiphany web browser](https://en.wikipedia.org/wiki/Epiphany_(GNOME)), using x86_64 processor architecture. This string can be manipulated in the client end. For example, a desktop client web browser can pretend to be a mobile browser. Default syntax for Apache log files follow the layout which is described [here](https://httpd.apache.org/docs/2.4/logs.html) under section 'Common Log Format'. **e)** Install and try out PhpMyAdmin or similar out-of-the-box web-based database management solution. -------------- **Answer:** **NOTE!** We assume that the established Linux system has a working Apache HTTP daemon web server and PHP 5.0 or PHP 7.0 script language packages installed as Apache modules (see tasks a) and c) above). 1. [phpMyAdmin](https://en.wikipedia.org/wiki/PhpMyAdmin) requires MySQL/MariaDB database back-end server. Let's install MySQL database server with its runtime dependencies on Debian-based distribution (Ubuntu 16.04.3 LTS in this case). ``` sudo apt-get -y install mysql-server ``` 2. Local administrator (assumed to belong to sudo group) is asked to set a new password for root MySQL database root account: **NOTE!** This description applies to Ubuntu 16.04 LTS. Newer OS releases may not ask you to set MySQL password after MySQL server installation. If you don't see a blue screen stating "While not mandatory...", run the following command instead: ``` sudo mysql_secure_installation ``` ``` Configuring mysql-server-5.7 While not mandatory, it is highly recommended that you set a password for the MySQL administrative "root" user. If this field is left blank, the password will not be changed. New password for the MySQL "root" user: ``` We should choose a strong password ('Hu8aS1n?tI23' or 'Tu$pAR!iMu65' etc. Can be generated with a password generator program _pwgen_ as well). We should use a password which we don't use in other environments. **NOTE!** If unsure, check and configure your keyboard layout so that the password you supply will be correctly set up. When you have entered your password, press _TAB_ and then press __ (or just _ENTER_). Type the password again and continue. 3. Let's install [phpMyAdmin](https://en.wikipedia.org/wiki/PhpMyAdmin) with its runtime dependencies on Debian-based distribution (Ubuntu 16.04.3 LTS in this case). ``` sudo apt-get install -y phpmyadmin ``` Accept the installation with extra dependencies. 4. On Debian-based systems, the next screen states: ``` Configuring phpmyadmin Please choose the web server that should be automatically configured to run phpMyAdmin. Web server to reconfigure automatically: [] apache2 [] lighttpd ``` Because we assume that you have Apache2 and not lighttpd, we should select apache2. Press _SPACE_ so that option _apache2_ has asterix in the brackets ([*]). After that, press _TAB_ to move the selection to __ and press _ENTER_. 5. The next screen states: ``` Configuring phpmyadmin The phpmyadmin package must have a database installed and configured before it can be used. This can be optionally handled with dbconfig-common. If you are an advanced database administrator and know that you want to perform this configuration manually, or if your database has already been installed and configured, you should refuse this option. Details on what needs to be done should most likely be provided in /usr/share/doc/phpmyadmin. │ Otherwise, you should probably choose this option. Configure database for phpmyadmin with dbconfig-common? ``` The most common selection in this step is __ (alternative to __). For choosing a right choice, the following manuals can be used: [Configure the database with dbconfig-common](https://docs.openstack.org/ocata/fr/install-guide-debconf/debconf/debconf-dbconfig-common.html) [dbconfig-common.pdf](https://www.debian.org/doc/manuals/dbconfig-common/dbconfig-common.pdf) [Stackoverflow - What means dbconfig-common](https://stackoverflow.com/questions/31403273/what-means-dbconfig-common) Additionally, for example: ![dbconfig-common_1](https://docs.openstack.org/ocata/fr/install-guide-debconf/_images/dbconfig-common_keep_admin_pass.png) ![dbconfig-common_1](https://docs.openstack.org/ocata/fr/install-guide-debconf/_images/dbconfig-common_used_for_remote_db.png) Let's choose option __ because we don't have earlier database in our system. More secure, although more troublesome, approach would be selecting __, according to the material references above. **NOTE!** dpkg tool _dpkg-reconfigure_ makes it possible to change package-related configuration in Debian-based systems afterwards (for example, _sudo dpkg-reconfigure phpmyadmin_). However, pay special attention and use extra care if you do any configurations afterwards. 6. The next screen asks system administrator to set up MySQL database-related password for phpmyadmin MySQL user: ``` Configuring phpmyadmin Please provide a password for phpmyadmin to register with the database server. If left blank, a random password will be generated. MySQL application password for phpmyadmin: ``` Let's pick up a strong password for phpmyadmin. Take a look on step 2. for setting up a strong password. Use different password here. 7. If you are not asked to set up a default phpmyadmin username or password, please run _sudo dpkg-reconfigure phpmyadmin_. The default phpmyadmin username is usually 'phpmyadmin' and the login password is the one you write down by executing the command mentioned earlier in this step. 8. Let's try accessing phpMyAdmin front page: ``` xdg-open http://localhost/phpmyadmin ``` If the installation has been successful, the web browser view should look like this: ![phpmyadmin-loginpage](https://rootninja.files.wordpress.com/2010/10/image38.png) 9. Log in to phpMyAdmin with the following credentials: - **user name:** phpmyadmin (or any other defined during phpmyadmin installation) - **password:** password defined in step 7. 10. If successfully logged in, the web browser view should be as follows (phpMyAdmin - Front page): ![phpmyadmin-frontpage](https://github.com/Fincer/linux_server_setup/blob/master/images/phpmyadmin-resized.png) phpMyAdmin - Database configuration sample: ![phpmyadmin-dbconfpage](https://github.com/Fincer/linux_server_setup/blob/master/images/phpmyadmin-2-resized.png) **f)** Create a web application which executes a simple calculation for the user (BMI calculator, for example) -------------- **Answer:** **NOTE!** We assume that the Linux installation has a working Apache HTTP daemon web server with correct PHP support enabled. Apache server has 'userdir' module enabled, and some local user has _public_html_ folder created. 1. Let's create the following, very simple PHP code and save it to ~/public_html/rainfall.php (current user) ``` ``` 2. Let's open the php file with the default graphical web browser found in the local Linux system: ``` xdg-open http://example.com/~$(whoami)/rainfall.php ``` Result: ![php-sample-image-1](https://github.com/Fincer/linux_server_setup/blob/master/images/php-example-rainfall.png)