Export (0) Print
Expand All

Security Concerns for Visual Basic .NET and Visual C# .NET Programmers

Visual Studio .NET 2003
 

Robin Reynolds-Haertle
Visual Studio Team
Microsoft Corporation

January 2002

Summary: This article focuses on key security issues that Visual Basic .NET and Visual C# .NET developers need to address as they begin working with the .NET Framework. This overview discusses both Windows and Web applications, and the implementation, debugging, and deployment phases of development.

The article applies to the final release versions of Visual Studio .NET and the .NET Framework. If you are using prerelease versions, your applications may not work exactly as described in this document. (12 printed pages)

Contents

Introduction
Code Access Security
   Full Trust
   Partial Trust
   Developing for Partial Trust Environments
   Testing
   Additional Resources
Web Applications
   Dynamic Discovery
   Authentication, Impersonation, and Delegation
   ASPNET Process Identity
   Securing File Resources When Running Under the ASPNET Identity
   Debugging with the ASPNET Identity
Security Mechanisms in the Visual Studio .NET Development Environment
Conclusion

Introduction

Microsoft® Visual Studio® .NET provides you with more control over the security of running applications than previous versions of Visual Studio. While the .NET Framework provides more control, it also requires more responsible programming on your part. There are security issues that you need to address to create friendly and usable applications for your users.

There are three common situations where you will need to address security concerns:

  • The user running your application may deny privileges to your application, because your application is running from a location that the user has specified will not be allowed access to some system resources. For example, the user can configure the common language runtime to deny file privileges to any application that is stored on a network drive. You need to be aware of this as you code, and you should write code that responds graciously to denials.
  • Users accessing your Web applications from your Web servers need to be prevented from running malicious code or corrupting data on your servers.
  • The way you set up Visual Studio can leave your server more or less at risk from attacks by malicious code.

Code Access Security

Code access security is the system of the .NET Framework that controls access to resources by controlling the execution of code. This security feature is separate from, and in addition to, the security provided by the operating system. When a user runs your application, it will be assigned to a zone by the .NET common language runtime. The five zones are:

  • My Computer — application code is hosted directly on the user's computer.
  • Local Intranet — application code runs from a file share on the user's intranet.
  • Internet — application code runs from the Internet.
  • Trusted Sites — applications from sites defined as "Trusted" through Internet Explorer.
  • Untrusted Sites — applications from sites defined as "Restricted" through Internet Explorer.

The assignment of the first three zones, My Computer, Local Intranet, and Internet is based on where the code is located. You can override the assignment by assigning specific sites to the Trusted Sites or Untrusted Sites groups in the Internet Explorer.

Each of these zones has specific access permissions set by a system administrator. The security level for a zone may be set to full trust, medium trust, low trust, or no trust. The trust levels define the resources that are accessible to the application. The zone, together with other security evidence, such as the publisher, strong name, Web site, and URL of the code, determines the permissions that are granted to the code at run time. (For more information about security evidence, see Evidence.) You have no control over the security settings on the user's computer, yet your application must work within the settings it encounters as it runs. This may mean that your application will be denied access to particular resources. For example, your application may need to write data to a file, yet the user's system will deny write access at run time by raising an exception.

Your job is to develop your application to handle this situation. This does not necessarily mean that you will make your application find another way to write the data. It does mean that your application needs to anticipate that it may not be able to write the data and then respond to such a possibility. You may want to use more exception handling (Try…Catch in Visual Basic, or try…catch in C#) or some of the objects in the System.Security.Permissions namespace to make your code more robust. A brief description of these methods is included in a later section of this article, "Developing for Partial Trust Environments."

The security levels for zones are set using the Administration Tools installed when the .NET Framework is installed. To learn more about the setting security levels for zones on a computer, see Administration Tools.

Full Trust

Developers often work in a full-trust environment. They keep their source code on their hard drives, and they test their applications from their development computers. In this full-trust environment, any code the developer compiles is allowed to run on the local computer. Security exceptions do not surface, because the local computer is defined to be a full-trust environment by default.

Partial Trust

Partial trust describes any zone that is not a full trust zone. When an application is deployed, it may move into a new zone, perhaps one that does not grant full trust to the application. The two most common scenarios that run code in partial trust are:

  • Running code that is downloaded from the Internet.
  • Running code that resides on a network share (intranet).

Some examples of resources that might be denied in a partial-trust zone are:

  • File I/O, including reading, writing, creating, deleting, or printing files.
  • System components, such as registry values and environment variables.
  • Server components, including directory services, registry, event logs, performance counters, and message queues.

What is not allowed in partial trust? That is not easy to determine. Each class and each method of each class in the .NET Framework has a security attribute that defines the level of trust needed to run that method, and that attribute may not be accessible at run time because of just these security features. The zone level is not a simple mapping of trust level to attributes, but is a collection of particular permissions given to particular classes and methods. Your application will not be able to simply query for the trust level and then be able to predict which resources are not available. You can determine whether or not your application is running in full trust. One method will be described in the next section, "Developing for Partial Trust Environments."

Developing for Partial-Trust Environments

This section presents a very brief look into how security concerns might affect the code you write. There is no single solution to developing for the partial-trust environment. Your solution will depend on the application you are writing. Additionally, because the trust level may change during execution of your application, you cannot simply test for the existing trust level and then proceed.

The first step in developing for partial-trust zones is to write code that recognizes that security exceptions will surface. Consider the following code:

' Visual Basic
Public Sub MakeABitmap()
    Dim b As Bitmap = New Bitmap(100, 100)
    ' Some code here to draw a nice picture in the bitmap
    b.Save("c:\PrettyPicture.bmp")
End Sub

// C#
public void MakeABitmap()
{
    Bitmap b = new Bitmap(100, 100);
    // Some code here to draw a nice picture in the bitmap
    b.Save("c:\\PrettyPicture.bmp");
}

This method runs without throwing an exception if the project and the project assembly are stored on the hard disk of your computer, and if you are a member of the Administrators group on your computer. If you deploy this application to your intranet, a System.Security.SecurityException (see SecurityException Class) may be thrown when the application attempts to save the bitmap object. If you do not have a Try…Catch (in Visual Basic) or a try…catch (in C#) block around this code, your application terminates with the exception. This is probably not a satisfactory user experience. If you add exception-handling code, then your application can:

  • Warn the user that the application cannot complete all the tasks it needs to.
  • Clean up any existing objects, so that code that runs after the catch block does not fail.

You could modify the bitmap-saving code as shown below. The added code warns the user that that file was not saved because of a security denial, and it separates security failures from other file I/O failures, such as incorrect file names. This method does not create any security holes. The user will either modify security to trust your application, or the application will not run.

' Visual Basic
Public Sub MakeABitmap()
    Dim b As Bitmap = Nothing
    Try
        b = New Bitmap(100, 100)
        b.Save("c:\PrettyPicture.bmp")
    Catch ex As System.Security.SecurityException
        ' Let the user know the save won't work. 
        MessageBox.Show("Permission to save the file was denied, " & _
           "and the bitmap was not saved.")
    Catch ex As System.Exception
        ' React to other exceptions here.
        MessageBox.Show("Unable to create and save the bitmap.")
    End Try
End Sub

// C#
public void MakeABitmap()
{
    Bitmap b = null;
    try 
    {
        b = new Bitmap(100, 100);
        b.Save("c:\\PrettyPicture.bmp");
    } 
    catch (System.Security.SecurityException ex) 
    {
        // Let the user know the save failed. 
        MessageBox.Show("Permission to save the file was denied, " + 
            "and the bitmap was not saved.");
    } 
    catch (System.Exception ex) 
    {
        // React to other exceptions here.
        MessageBox.Show("Unable to create and save the bitmap.");
    }
}

Classes, attributes, and enumerations from the System.Security.Permissions namespace support even more control over the security tasks in your application. If you are writing libraries that may be called from other applications, you will want your library to verify the permissions of the calling code. For example, you could simply add the following assembly-level attribute at the top of your code file, or to the AssemblyInfo.vb or AssemblyInfo.cs file. For more information, see Setting Assembly Attributes.

' Visual Basic
<Assembly: System.Security.Permissions.FileIOPermissionAttribute( _
    System.Security.Permissions.SecurityAction.RequestMinimum, _
    Write:="c:\PrettyPicture.bmp")> 
 
// C#
[assembly: System.Security.Permissions.FileIOPermissionAttribute(
    System.Security.Permissions.SecurityAction.RequestMinimum, 
    Write="c:\\PrettyPicture.bmp")]

The runtime verifies the permission when the assembly loads. If the runtime denies the permission requested, the assembly fails to load and a security exception is thrown. If you add this attribute to a stand-alone application, the application might not run. If this attribute appears in a class library, then the library may not load at run time. You would need to add a try/catch block to the code that calls the class library.

You can also specifically ask for permissions from the runtime, shown below using the Demand method. The runtime may then grant or deny the demand. The demand is denied by raising a security exception. You might rewrite the code like this to explicitly demand permission to write the bitmap file:

' Visual Basic
Public Sub MakeABitmap()
    Dim b As Bitmap = Nothing
    Dim filename As String = "c:\PrettyPicture.bmp"
    Dim permission As New System.Security.Permissions.FileIOPermission( _
        System.Security.Permissions.FileIOPermissionAccess.Write, _
        filename)

    Try
        permission.Demand()
    Catch ex As System.Security.SecurityException
        ' Let the user know the save won't work. 
        MessageBox.Show("Permission to save the file was denied, " & _
           "and the bitmap was not saved.")
    Catch ex As System.Exception
        ' React to other exceptions here.
        MessageBox.Show("Other error.")
    End Try

    Try
        b = New Bitmap(100, 100)
        b.Save(filename)
    Catch ex As System.Exception
        MessageBox.Show("Unable to create and save the bitmap.")
    End Try
End Sub

// C#
public void MakeABitmap() 
{
    Bitmap b = null;
    string filename = "c:\\PrettyPicture.bmp";
    System.Security.Permissions.FileIOPermission permission = new 
        System.Security.Permissions.FileIOPermission(
        System.Security.Permissions.FileIOPermissionAccess.Write, 
        filename);

    try 
    {
        permission.Demand();
    }
    catch (System.Security.SecurityException ex) 
    {
        // Let the user know the save won't work. 
        MessageBox.Show("Permission to save the file was denied, " +
            "and the bitmap was not saved.");
    }
    catch (System.Exception ex) 
    {
        // React to other exceptions here.
        MessageBox.Show("Other error.");
    }

    try 
    {
        b = new Bitmap(100, 100);
        b.Save(filename);
    } 
    catch (System.Exception ex) 
    {
        MessageBox.Show("Unable to create and save the bitmap.");
    }
}

Testing

The second step in developing for partial-trust zones is testing in multiple environments, especially from your intranet and the Internet. This will force the security exceptions to be thrown. One important test is to create a user account on your local machine that does not have Administrator rights and try running your application with that account. For more information on setting up and running with such an account, see Developing Software in Visual Studio .NET with Non-Administrative Privileges.

Additional Resources

You have many more options than described in the overview above. These topics cover code access security in more detail:

Web Applications

Addressing Web application security protects your servers from malicious code and data corruption. There are several ways you can protect your server.

  • You can prevent users from finding and running your XML Web services by disabling dynamic discovery of your XML Web services.
  • Authentication allows you to verify the identity of a user before allowing that user to access your server.
  • Using the ASPNET process identity allows you to fine-tune the resources that are available to a user.

Each of these methods is discussed in more detail below.

Dynamic Discovery

Dynamic discovery is a feature of the .NET Framework that allows Web browsers to locate XML Web services running on a server. Once a user locates an XML Web service, the user can then call the methods of the XML Web service. This makes dynamic discovery a powerful ability for the user, but a potential security risk for your server. In most cases, you will not want dynamic discovery to be enabled, and by default, dynamic discovery is disabled when the .NET Framework is installed. This does not mean the XML Web services are not available, it just means that the server will not provide a directory of the available services. Clients will still be able to use XML Web services, but you will need to provide the exact location of the service to them.

Caution   With dynamic discovery disabled, you need to distribute the location of your XML Web services to your clients.

Two items control the discoverability of an XML Web service on the deployment server. The first item, the machine.config file, controls the overall discoverability of the server. The machine.config file, located in the \%windows%\Microsoft.NET\Framework\Version\Config folder, is an XML file that contains settings that control Web applications on the server. This file contains an element that is commented out by default. To enable discovery, you need to remove the comment characters. You also need to run your application using the ASPNET account, as described in the section "ASPNET Process Identity" below.

<!--<add verb="*" path="*.vsdisco" 
type="System.Web.Services.Discovery.DiscoveryRequestHandler, 
System.Web.Services, Version=1.0.3300.0, Culture=neutral, 
PublicKeyToken=b03f5f7f11d50a3a" validate="false"/>-->

The second item is a discovery file. The discovery file may be the default discovery file, default.vsdisco, or an XML Web service-specific discovery file. The discovery file is an XML file that contains information about the location of the XML Web service files.

To make a particular XML Web service discoverable, you need to enable discovery in the machine.config file and create and deploy a discovery file with the application. The discovery file is an XML file that simply lists the paths where XML Web services will not be found. An example is shown below. Complete instructions for creating and deploying this file can be found in Deploying XML Web Services in Managed Code.

<?xml version="1.0" encoding="utf-8" ?>
<dynamicDiscovery xmlns="urn:schemas-dynamicdiscovery:disco.2000-03-17">
     <exclude path="_vti_cnf" />
     <exclude path="_vti_pvt" />
     <exclude path="_vti_log" />
     <exclude path="_vti_script" />
     <exclude path="_vti_txt" />
     <exclude path="Web References" />
</dynamicDiscovery>

You can provide static discovery for an XML Web service by publishing a .disco file. A .disco file is an XML document that can contains links to other discovery documents, XSD schemas, and service descriptions. This is a more secure option, because you can control the information that is released. For information on creating and deploying a .disco file, see Enabling Discovery for an XML Web Service.

For more information on enabling dynamic discovery, see Enabling Discovery for an XML Web Service and Fine-Tuning Discovery Mechanisms.

Authentication, Impersonation, and Delegation

By default, Web applications will run in an anonymous mode, meaning the application has no information about the identity of the user. This is fine for sites that contain public information. If you want to control who has access to your application or other resources, then add authentication to your application. Authentication is the process of identifying the user of your application and verifying that the user is authorized to access your application. There are several methods of authentication supported by ASP.NET. The most commonly used methods are:

  • Anonymous   No identity information is requested from the user. This method is appropriate for Web sites with public content. If personalization of the site is needed, cookies can be used. For information on using cookies in ASP.NET applications, see Introduction to Web Forms State Management.
  • Forms   The application presents the user with a login form that requires login information, such as name and password. The form is posted back to the server, where the information is compared with a data store.
  • Basic   Basic authentication is configured using Internet Information Services (IIS) and is supported by most browsers. When enabled, the browser prompts the user for a name and password, and then passes the information back to the ASP.NET application using Base64 encoding, which is easily deciphered. This method requires that users have Windows accounts. Using Secure Sockets Layer (SSL) in addition to the Basic authentication can secure this authentication method. For information about SSL support in ASP.NET, see Using Secure Sockets Layer.
  • Digest   Digest authentication is configured using IIS and is available on servers running Microsoft Windows® 2000 or Windows XP. Digest authentication provides a stronger level of password encryption than Basic authentication. This method requires that users have Windows accounts stored in Microsoft Active Directory®.
  • Integrated Windows   Integrated Windows authentication is similar to Basic and Digest authentication, with the exception that the user name and password are not passed back to the Web application. This method is especially appropriate in an intranet environment. It requires users to have Windows accounts and is only supported by the Internet Explorer browser.
  • Certificate   A certificate is a digital key installed on a computer. When the user attempts to access your server, the key is presented. The server then authenticates the certificate in a Domain or Active Directory. This method is appropriate for applications that require such a high degree of security that it offsets the cost of managing certificates.
  • Passport   Microsoft provides this centralized authentication service. Passport authentication is appropriate if your Web site is used with other Passport sites, thereby allowing a single sign-on to all the sites, or if you do not want to maintain a user database.

Authentication allows you to authorize the user of your application, but that is not necessarily enough to allow the user to access resources, such as files and databases. You can configure your resources such that they are available to particular users, not the Web application itself. In this case, you can use impersonation to allow users to access the resources. When using impersonation, the server process runs with the identity of the authenticated user. When your application is using impersonation and queries a database, the database application processes the query as though it came from the user, not the server. Impersonation is enabled by setting the impersonate attribute of the identity element in the application's Web.config file, as shown. A Web.config file is created as part of every Web application project.

<identity impersonate="true">

A step beyond impersonation is delegation, which uses the identity of the user in accessing remote resources (other computers). Not all authentication methods support impersonation, as shown in the follow table.

Supports delegation Does not support delegation
Basic Anonymous
Integrated Windows Digest
Certificate Passport
  Forms

For a detailed discussion of choosing and implementing an authentication method, see Authentication in ASP.NET: .NET Security Guidance. For more information on Web application security, see Web Application Security at Run Time.

ASPNET Process Identity

When your Web application starts running on a server, it does not run as though it is logged on as you, the author of the Web application. Instead, it runs as though it is logged on using one of the Windows user accounts defined on the server. That account, also referred to as the identity, is one of three accounts, the ASPNET identity, the SYSTEM identity, or a custom identity. The identity is specified in the machine.config XML file, located in the \%windows%\Microsoft.NET\Framework\Version\Config folder of the server. A simplified example of the element is shown below for the three configurations. The element in the file has several attributes that are not shown in this example.

<!-- Select the ASPNET identity -->
<system.web>
  <processModel enable="true" username="MACHINE" password="AutoGenerate"/>
</system.web>

<!--  Select the SYSTEM identity -->
<system.web>
  <processModel enable="true" username="SYSTEM" password="AutoGenerate"/>
</system.web>

<!--  Select a custom identity -->
<system.web>
  <processModel enable="true" username="domain\user" password="pwd"/>
</system.web>

ASPNET is the default identity selected when the machine.config file is installed with the .NET Framework. The ASPNET account is a member of the Users group, a group that by default has only minimal privileges. The ASPNET account also has a few other privileges, including full privileges to the ASP.NET and Windows temporary directories.

If you change the identity to SYSTEM, then your application runs under the System identity, which has the privileges of the Administrators group. The System account has access to nearly all the resources of the server.

Caution   Running under the SYSTEM identity places your server at the highest risk for attacks from malicious code and data corruption.

To use a custom identity, you must create the account and configure its privileges in a specific way. For information on creating a custom identity, see Authentication in ASP.NET: .NET Security Guidelines.

Several system resources are not available by default to the ASPNET account. Common restrictions and solutions are outlined below. It is recommended that you use the ASPNET account and the solutions described, rather than run the application under the System identity.

  • File resources   You can adjust the file and folder privileges granted to the ASPNET account by accessing the Access Control Lists (ACLs) for individual files and folders through Windows Explorer. The changes to the ACLs for ASPNET are not automatically propagated through deployment. For example, you may have allowed write permissions for the ASPNET account on the file c:\picture.bmp on your development computer. When you deploy your application, it will run on a different computer that also has an ASPNET account. You will need to add write permission for the ASPNET account on the deployment computer for the c:\picture.bmp file on the deployment computer. Fortunately, you can make changes to ACLs using custom actions in the deployment project, but you must keep track of the changes that need to be made.
  • Event logs   The ASPNET account cannot create new event-log categories, though it can add entries to an existing log. You can use impersonation with the ASPNET account to allow creation of new event-log categories. The impersonation identity must have sufficient privileges to create event-log categories. If your application needs event logs that can be specified before production, they can be created by the deployment project.
  • Directory Services and Active Directory   Access to these requires impersonation and delegation, or that specific security credentials are passed to the DirectoryEntry objects. If you choose to pass specific credentials to the DirectoryEntry objects, you need to ensure that the information is stored appropriately.
  • Performance counters   The ASPNET account can write to but not read from performance counters, and it cannot create new performance-counter categories. You can use impersonation with the ASPNET account to allow creation of new performance-counter categories. The impersonation identity must have sufficient privileges to create performance-counter categories. If your application needs performance counters that can be specified before production, they can be created by the deployment project.

Securing File Resources When Running Under the ASPNET Identity

Note   The instructions in this section apply to systems running the NTFS file system. If your server is running with the FAT32 file system, consult the file system documentation for information on securing files.

By default, the ASPNET account has only the read and execute privileges of the Users group. If your Web application needs to write to or create new files, then you can grant permissions to specific files and folders by modifying the Access Control Lists (ACLs). You can access the ACLs for a file by right-clicking the file in Windows Explorer, selecting Properties, and then selecting the Security tab. It is preferable to modify the ACLs for specific files, rather than add general privileges to the ASPNET account. The permissions you will probably be most concerned with are:

  • Read — Data files and executable files need read permissions.
  • Write — Data files that are updated by the application need write access.
  • Execute — In Web applications, the .asmx files are executable files.
  • Create — To create files, you need to add create permissions for the folder where the file is to be created.

These privileges apply to:

  • Files
  • Folders
    Caution   The particular combination you want to avoid is allowing writing or creating along with executing on the same file or directory. In this case, a user might find a way to write malicious code to the file and then execute it.

Here are some tips for simplifying the permissions process:

  • Separate the files in your application into directories, based on the permissions that the files need. For example, if you put only read/execute files in one directory, then you only need to set the permissions on one directory, rather than on several files. Consider setting up separate directories in the application for read/execute, read/write, read/create.
  • Your application may contain a data file that is empty at deployment, and your application contains code to create the file the first time it is run. This would require you to add create permissions to a directory, an elevated security setting. Instead of adding code to create the new empty file, consider distributing an empty file with the application at deployment. In this way, you only need to add write permissions to the file, rather than add create permissions for the directory.

As you develop your application, you will be adding permissions to files and directories on your development computer to fine-tune the security level of your application. Unfortunately, the ACLs will not be transferred to corresponding files on the deployment computer. To plan for the transfer of these ACLs, keep these issues in mind:

  • You can use custom actions in the deployment package to change the ACLs. For more information, see Custom Actions.
  • Third-party tools are available for tracking and locating the changes you have made.
  • You are probably going to have to justify each setting with the system administrator. You will need to document why the change is necessary, and why you cannot do it in a way that does not require a change in the setting.

Debugging with the ASPNET Identity

When you are debugging an XML Web service, the XML Web service will be called with the ASPNET identity, if that is how you have defined the identity in the machine.config file. The ASPNET identity is not a member of the Debugger Users group by default (see next section, "Security Mechanisms in the Visual Studio .NET Development Environment"), so you will not be able to step into the XML Web service code during debugging. To debug an XML Web service, open the code for the XML Web service and set a breakpoint.

It is recommended that you debug on a test computer, rather than the deployment computer. This is discussed in the next section, "Security Mechanisms in the Visual Studio .NET Development Environment."

For more information on configuring the process identity, see ASP.NET Process Identity, and ASP.NET Impersonation.

Security Mechanisms in the Visual Studio .NET Development Environment

In addition to protecting your servers, you want to protect your development computers from attacks of malicious code and data corruption. There are several mechanisms in the development environment you can take advantage of to help secure your development servers:

VS Developers and Debuggers   These two account groups are added when Visual Studio .NET is installed. The VS Developers group has the necessary file, share, and IIS permissions to create and develop Web applications on a server. The Debuggers group has the ability to debug processes on a particular computer, either locally or remotely. Both groups are powerful users of the server, having access to most resources. For more information, see Web Application Security at Design Time in Visual Studio.

Debugging   It is recommended that you debug on a test computer, rather than the deployment computer. If you must debug on a deployment server, install only the Remote Debugging Component and uninstall the component when you are done debugging. Take the server offline when you are debugging. For more information, see Introduction to Web Application Debugging.

Deployment   For most applications, it is sufficient if the .NET Framework alone is installed on the server. If Visual Studio .NET or the Visual Studio .NET Server Components are installed on the deployment computer, then the VS Developers and Debuggers groups will all exist on the deployment computer. You will need to restrict the members of VS Developers and Debugger users. In addition, you may also want to disable dynamic discovery.

Caution   It is strongly recommended that you not install Visual Studio .NET on your deployment server. The Visual Studio .NET setup adds both files and users to your system that can be exploited. You can secure a system that has Visual Studio .NET installed, but if you do not need Visual Studio .NET on the deployment server, it is recommended that you not install it there.

The Copy Project feature of Visual Studio .NET includes the option to deploy an application with a configuration file (Web.config) that is different from the configuration file used in development. It is likely that the development file has debugging enabled, which if deployed, would allow users to examine the call stack when an exception is thrown. It is recommended that you deploy with a separate configuration file that does not allow debugging.

Conclusion

Securing resources is a process that spans several technologies and the entire development cycle. Through careful design, implementation, testing, and deployment of applications, you can create very secure applications. Security technologies, provided by ASP.NET, the operating system, and Web browsers are available to secure your applications.

Show:
© 2014 Microsoft