Luiz Bossoi

SQL Injection – Você sabe se proteger?

Bom, depois de muito tempo sem postar, hoje vou falar um pouco a respeito de um problema que estou presenciando com mais freqüência a cada dia que passa por parte dos outros programadores.

Para quem usa Frameworks que desenvolve o sistema inteiro praticamente sozinho, provavelmente não vão saber nem do que estou falando.

Primeiramente uma breve explicação do que é SQL e SQL Injection

“Structured Query Language (SQL) é uma linguagem textual usada para interagir com uma base de dados relacional. A unidade de execução do SQL é uma ‘query’, que é uma coleção de instruções que retornam uma pesquisa na base de dados. Os comandos e parâmetros de uma ‘query’ podem modificar a estrutura da base de dados (usando instruções Data Definition Language, ou DLL e manipulando o conteúdo dos bancos de dados. (usando insturções Data Manipulation Language, ou DML).

SQL Injection ocorre quando o atacante consegue inserir uma série de intruções SQL dentro de uma ‘query’ através da manipulação das entrada de dados de uma aplicação.” (Wikipédia)

Acredito que normalmente as pessoas que fazem este tipo de “arte” contra uma aplicação vulnerável ao ataque SQL Injection são também programadores que em algum momento da sua vida quer verificar a confiabilidade de especificados sites.

Eu já passei por experiências de deface com sites que eu desenvolvi devido à correria em que os projetei e algumas brechas acabaram ficando abertas.

Felizmente para isso existe o backup, bastou restaurar o backup do banco de dados e tudo ficou bem. Assim que percebi que algo faltava no banco de dados, a primeira coisa que me veio a cabeça foi que eu havia deixado um erro de projeto em algum momento em minha codificação, então eu simplesmente arrumei a programação.

Eu não considero SQL Injection como crime de invasão, deface ou variantes pelo simples motivo que o sujeito que faz a arte do SQL Injection na aplicação de terceiros não sabe qual será a reação do tratamento da aplicação perante sua ação.

Como a legislação brasileira ainda não é totalmente clara a respeito de crimes virtuais, algo tão complicado como o defacing por SQL Injection é algo complicado de se atuar.

Bom, não sou advogado e este é apenas meu ponto de vista, de qualquer forma irei utilizar a clássica frase do “não faça coisas erradas”: Que o conteúdo explicado aqui seja apenas para fins educacionais.

O exemplo do ano a respeito do SQL Injection é o Vinicius Kmax que explorou uma vulnerabilidade do ambiente da Telefônica (Speedy) e com isso acabou divulgando para todos dados particulares dos seus clientes.

http://idgnow.uol.com.br/mercado/2009/08/31/advogado-de-vinicius-kmax-defende-que-telefonica-deveria-contrata-lo/

Normalmente quando eu desenvolvo um site para a Internet eu sempre fecho todas as possibilidades de ataques SQL Injection, porém quando acontece de esquecer algum lugar devido a correria do projeto, eu relativamente “gosto” de ser atacado para cada vez mais eu possa ter atenção e não fazer projetos correndo só porque o cliente está com pressa.

(Qualidade de Software) + Pressão do Cliente + Pressa + Correria = Fórmula para o desastre.

Normalmente a faculdade ao formar um profissional acaba não informando estes tipos de vulnerabilidades, talvez até por desconhecimento do tutor ou por achar que não existe necessidade de explicar tal tipo de problema.

Daí o vulgo profissional vai trabalhar em uma empresa grande, com um excelente salário e ao enfrentar problemas como “Putz minha tabela do banco de dados sumiu!”, a culpa é de quem?

Caso conhecimento do profissional for limitado, vai restar ligar para o pessoal da TI e falar que o servidor foi invadido, que está com problemas, que o banco de dados foi corrompido, que o serviço de hospedagem prestado é uma porcaria.

Se você trabalha com tecnologia, acompanhe a sua evolução. O que você aprendeu ontem, já não serve mais hoje.

Se Googlearem encontrarão muita coisa a respeito de SQL Injection na internet, porém vou explicar com minhas palavras este tipo de arte.

1. Entendendo a estrutura e lidando com instruções SQL Injection

Iremos utilizar para este exemplo o banco de dados SQL Server pelo motivo de ser mais simples de entender as mensagens de erro e poder entender como está estruturada a Query da aplicação.

Antes de qualquer coisa, o SQL Server por este motivo não é vulnerável, ou seja, é um excelente serviço de banco de dados. A culpa de você ser atingido um dia com SQL Injection é totalmente sua ao programar a aplicação e não do seu serviço de banco de dados.

Vamos supor que você tenha uma página de Login, com usuário e senha como a imagem abaixo:

Qual seria seu script para validar este tipo de login? Acredito que algo parecido com meu código abaixo:

  1.  
  2. <%
  3. usuário  = request.form("usuário")
  4. senha   = request.form("senha")
  5.  
  6. Set  Recset = db.execute("Select * from tbl_usuarios where usuário = ‘" & usuário  & "’ and senha = ‘" & senha & "’")
  7. If Recset.eof=true then
  8. response.write "Dados incorretos!"
  9. Else
  10. Response.write  "Logou!!!
  11. End  If
  12. %>
  13.  

Tudo bem até aqui certo? Errado!

Note que as varáveis usuario e senha recebem o conteúdo vindo do formulário diretamente.

Imagine agora que um invasor tente se aproveitar da falha do seu sistema para efetuar um login no seu site sem ser autorizado, como ele faria isso?

Vamos supor que o invasor digite o usuário LUIZ e a senha ele digite a seguinte String:

  1.  
  2. asd‘ OR ‘a‘=’a
  3.  

Qual será o efeito desta SQL na aplicação?

Seu comando SQL será executado da seguinte forma:

  1.  
  2. SELECT * FROM tbl_usuarios WHERE usuário = ‘LUIZ’ AND senha = ‘asd’ OR ‘a’=‘a’
  3.  

Notaram a atuação do nosso invasor?

Através do formulário ele informou a nossa instrução SQL uma condição OR válida que fez com que nossas condições AND fossem descartadas completamente.

Com isso o Recset não será mais End of File (EOF) e consequentemente o Resutset retornará praticamente um Select sem a condição Where (irá retornar todos os registros do banco).

Assim, o usuário será logado sem ser registrado!

Quem dera se o SQL Injection se baseasse apenas em páginas de Login não?

Podemos destruir completamente sites se aproveitando deste tipo de falha.
Imagine que o invasor dê um DROP TABLE tbl_Clientes por SQL Injection em seu site e você não tem backup, irá perder todos os clientes cadastrados na tabela tbl_Clientes

Mas como o invasor irá destruir meu site se ele não sabe o nome das minhas tabelas?
Simples, o SQL Server retorna um erro muito específico ao ser estressado com um tipo de SQL que irá retornar um erro.

Por exemplo se o invasor ao invés de digitar aquilo na senha como foi falado anteriormente digitar:

  1.  
  2. asd’ HAVING 1=1;
  3.  

Um erro parecido com este irá retornar na tela do mesmo (e este erro sua aplicação que transmitiu)

[Microsoft][ODBC SQL Server Driver][SQL Server]Column ‘TABELA.CAMPO‘ is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.

Pronto, a partir deste erro você pode saber qual é o nome da tabela que está sendo referenciada a SQL.

E agora?

O invasor digitará no seu site no campo de senha algo parecido com:

  1.  
  2. asd’ ; DROP TABLE TABELA ;—-
  3.  

Pronto, a tabela “sumiu” do seu banco de dados! J

O invasor poderia deletar os registros também ao invés de Dropar a tabela, ou então poderia alterar registros, enfim, teria controle total como se estivesse administrando este banco de dados.
Para corrigir estes problemas em STRINGS podemos utilizar um replace de aspas simples para outro caractere caso precisemos trabalhar com ela posteriormente ou então retira-las completamente do sistema:

  1.  
  2. <%
  3. function limpa(str)
  4. str = replace(str,"’","")
  5. limpa = str
  6. end function
  7.  
  8. response.write limpa("Este é um teste, me dá um copo d’agua!")
  9. %>
  10.  

O resultado será:

Este é um teste, me dá um copo dagua!

Sendo assim, todas suas variáveis devem ser tratadas da seguinte forma, voltando a aplicação de exemplo:

  1.  
  2. <%
  3. usuário  = limpa(request.form("usuario"))
  4. senha   = limpa(request.form("senha"))
  5.  
  6. Set  Recset = db.execute("Select * from tbl_usuarios where usuario = ‘" & usuário  & ‘" and senha = ‘" & senha & "’")
  7. If Recset.eof=true then
  8. response.write "Dados incorretos!"
  9. Else
  10. Response.write  "Logou!!!"
  11. End  If
  12. %>
  13.  

É importante também tratarmos variáveis que são passadas pelo método GET principalmente, que é mais fácil do invasor atuar, já que ele precisa digitar apenas as instruções diretamente na URL.

  1.  
  2. <%
  3. código = limpa(request.querystring("cód"))
  4. response.write "O código do produto a  ser exibido é: " &amp; codigo
  5. %>
  6.  

Para programadores PHP, é disponibilizada a função addslashes() para evitar este tipo de problema.

Existe um software chamado “Acunetix Web Vulnerability Scanner 6″ que é excelente para identificar problemas relacionados a SQL Injection, pois ele scaneia seu site inteiro a procura de vulnerabilidades.

Com softwares como estes podemos identificar se nossa aplicação tem qualidade antes de enviarmos para o ambiente de produção.

Bom com isso encerro aqui e espero que tenha sido claro este tipo de problema que muitos programadores acabam enfrentando e não tem idéia de como solucionar, ou simplesmente não sabem o motivo de algumas tabelas ou dados terem sumidos ou sido alterados.

Qualquer dúvida, sugestão, crítica ou complementação a respeito, deixe um comentário!

Componente DLL – Biblioteca de Funções [ASP]

Bom hoje vim trazer uma coisa relativamente importante para o Blog e para quem sempre tem problemas para encontrar funções específicas e acaba desenvolvendo por conta!
Chega de reclamar das aulas como no post anterior (que por sinal ficou enorme não?)

Antes de qualquer coisa, adicionei um link novo e muito interessante a lista de links do Blog que é o site do macoratti, um site recheado de conteúdo para programadores específicos de linguagens da Microsoft. O site tem uma enorme quantidade de conteúdo e foi nele e em outros sites que à muito tempo atrás eu aprendia a programar.

Bom, trouxe hoje uma DLL que eu algumas vezes uso e que possui uma coletânea de quase todas as funções que usamos para programar.
Uso ela em aplicações em ASP (VBScript) e ela também foi feita em VB, talvez seja útil para vocês para alguma coisa assim como é as vezes para mim.

Documentação da DLL:

  1.  
  2. ‘// Função PING interpretada por componente
  3. Function PingServer(servidor As String, Optional hopes As Integer = 0, Optional tamanho As Integer = 0) As String
  4.  
  5. ‘//Função para verificar se o servidor está Online
  6. Function CheckOnline(servidor As String) As Boolean
  7.  
  8. ‘//Função para Listar Conteudo do Diretório
  9. Function ListDir(diretorio As String)
  10.  
  11. ‘//Função para Criar Pastas
  12. Function CreateDir(localizacao, diretorio As String)
  13.  
  14. ‘//Função para Remover Pastas
  15. Function DeleteDir(localizacao, diretorio As String)
  16.  
  17. ‘//Função para converter ISO para UTF
  18. Function ISOtoUTF(texto As String) As String
  19.  
  20. ‘//Função para colocar em caixa alta a primeira letra
  21. Function FirstUPCASE(palavra As String) As String
  22.  
  23. ‘//Função para colocar uma letra alta e uma letra baixa
  24. Function DinamicCASE(palavra As String) As String
  25.  
  26. ‘//Função para converter a data para o MYSQL
  27. Function MYSQLConData(Data As String) As String
  28.  
  29. ‘//Função para Resumir uma String
  30. Function StringResume(texto As String, LenTexto As Integer) As String
  31.  
  32. ‘//Função para retornar a data completa
  33. Function CompleteDate() As String
  34.  
  35. ‘//Função para evitar SQL INJECTION
  36. Function CleanString(texto As String) As String
  37.  
  38. ‘//Função para Validar Email
  39. Function isEmail(email As String) As Boolean
  40.  
  41. ‘//Função para ValidarCPF
  42. Function isCPF(Num_Cpf As String) As Boolean
  43.  
  44. ‘//Função para Validar CNPJ
  45. Function isCNPJ(Num_Cnpj As String) As Boolean
  46.  
  47. ‘//Função para retornar a quantidade de vogais
  48. Function countVogals(palavra As String) As Integer
  49.  
  50. ‘//Função para contar o total de palavras
  51. Function countPalavras(texto As String) As Integer
  52.  
  53. ‘//Função para validar dominio
  54. Function isDomain(Dominio As String) As Boolean
  55.  
  56. ‘//Função para extrair somente o dominio de uma URL
  57. Function extractDomain(URL As String) As String
  58.  

Acho que isso é o suficiente para quem vai utilizar ela no dia a dia, já que vocês precisam saber apenas o retorno e os parâmetros que devem ser passados para a função.

Para instanciar a DLL na programação em ASP você deve chamar o procedimento CREATEOBJECT do Servidor, o código abaixo exemplifica como fazer:

  1.  
  2. <%
  3.     Set ComponenteLuiz = Server.CreateObject("LuizBossoi.SysFunc")
  4.     Set ComponenteLuiz = nothing
  5. %>
  6.  

Após isso, basta chamar as funções como precisar, alguns exemplos:

  1.  
  2. <%
  3.     Set ComponenteLuiz = Server.CreateObject("LuizBossoi.SysFunc")
  4.         With response
  5.          .write ComponenteLuiz.CheckOnline ("www.uol.com.br") & "<BR>"
  6.          .write ComponenteLuiz.CompleteDate & "<BR>"
  7.          .write ComponenteLuiz.FirstUPCASE("luiz roberto")
  8.         end with
  9. %>
  10.  

Retorna:

  1.  
  2. True
  3. quinta-feira, 16 de abril de 2009
  4. Luiz Roberto
  5.  

Espero que esta DLL seja útil para todos vocês assim como é para mim quando preciso de alguma função extra.
Como ela fica registrada no servidor todas as aplicações que forem desenvolvidas para rodar no mesmo servidor podem usar as funções do componente, fazendo com isso o reaproveitamento do código.

-> O download da DLL está disponível aqui, clique aqui para baixar.

Vou verificar se na próxima update trago um conteúdo relacionado com administração de sistemas ou algo relacionado com banco de dados.

Fiquem com o Achmed agora, o vídeo é velho, mas vale a pena ainda ver!

Um abraço e até a próxima

Validação de Código de Barras EAN13 – Pascal

Bom, acabei de voltar da prova que achei a mais absurda (desde a matéria que seria cobrada até na aplicação que usamos para desenvolver a prova).

O primeiro cumulo do absurdo foi que o semestre todo e inclusive o ano passado, desenvolvemos todos projetos em Delphi (e que fique claro aqui que não vejo nenhuma graça em Delphi) usando o CODEGEAR RAD Studio (Delphi 2007) que com certeza é uma plataforma que eu e muitos preferem para desenvolver aplicações em Delphi do que o tradicionalzão Delphi 7, já que o CODEGEAR querendo ou não torna a vida do programador mais fácil que o normal, completando quase todas as sentenças automaticamente, criando variáveis e gerando praticamente toda a rotina de programação para o programador e hoje foi diferente!

Chegamos na aula e depois de muita demora fomos surpreendidos com todos computadores sem o Delphi estar aberto e com o Switch da rede desligado, e como na faculdade para abrir o CODEGEAR precisa ter a rede ligada para se comunicar com o servidor, ficamos no Delphi 7! :)

O melhor de tudo é o professor colocar a culpa no servidor que não tem nada haver com isso, já que o problema era o Switch desligado…ou seja, quem não sabia trabalhar no Delphi 7 porque desacostumou ou se aproveitava das ferramentas automaticas do CODEGEAR, dançou!

A segunda coisa absurda que achei foi com com relação ao código de barras que tinha que ser decorado por todos para conseguir fazer a prova, ou seja, decorem todo algoritmo de calculo do dígito verificador e façam a prova!
Na faculdade e na vida aprendi que boas programações são aquelas que podemos desenvolver de uma forma que o código possa ser reutilizado e na vida aprendi que para que inventar a roda se já inventaram?

Vou explicar melhor: O algoritmo que tivemos que decorar é usado apenas para calcular o dígito verificador dos códigos de barra da GS1 Brasil (http://www.gs1brasil.org.br/) e gerar os códigos de barras.
Agora a questão é: Esse conteúdo é um conteúdo que realmente fará diferença na vida de todos programadores que fazem esse curso de graduação?
Ao questionar o professor porque tinha que decorar esse código o motivo foi : “Um dia vocês vão precisar”, retrucamos falando que o Google está ai para isso e novamente “Em uma entrevista de emprego vocês irão precisar”. (????)

Código de barras é utilizado em todos lugares e eu entendo isso, mas isso é uma coisa que você usa uma vez na vida e esquece, ou iremos trabalhar apenas gerando códigos de barra o resto da vida?

Criar componentes, criar templates, criar DLL, são coisas que realmente irão um dia fazer a diferença em nossa vida de programadores, mas o uso de código de barras e esta pura decoreba na prova realmente não leva em lugar algum.
Aprender este tipo de conteúdo apenas para conhecimento tudo bem, mas isso não é uma coisa que deve ser levada tão a sério.

Antes de qualquer coisa, este tipo de código para verificação do dígito é bem simples e não tive problemas em “decorá-lo”, apenas não vejo motivo de ter sido aplicado este tipo de conteúdo para a prova (e que levou 12 aulas para concluir = ~~40horas), ou seja, tempo perdido, amanha todo mundo já esqueceu!

Bom, esta matéria se chama AUTOMAÇÃO COMERCIAL, e outra coisa que achei interessante foi o trabalho que entregamos a uns dias atrás que era um sistema com TEF integrado (Transferencia Eletronica de Fundos).
No caso foi passado para os alunos várias coisas em Delphi como algumas que já falei (componentes, templates…) e em um belo dia mal dormido pelo professor, ele diz: “O trabalho tem que ter TEF e eu não vou ensinar, vocês vão ter que se virar”..

Até entendo essa atitude clássica de “Despertar o lado curioso e fuçador dos alunos”, mas não é bem isso que acontece.
Quando o primeiro trabalho em TEF foi concluido, ele rolou todas as turmas de análise de sistemas, e quando o outro ficou pronto, rolou mais ainda entre todos, mais uma vez, atitude mal pensada pelo professor, intenção perdida.
Na verdade, não foi tão simples assim aplicar o TEF ao trabalho, foram gastos dias e dias de pesquisa de como funciona o sistema, dias procurando lugares para baixar os arquivos de cartão de crédito na internet, tentando entender a rotina do funcionamento do TEF, aprendendo regras fiscais que não foram explicadas na aula, etc.

Todos que realmente fizeram o TEF, nem que fosse meio na gambiarra, viram que apenas implementar o TEF ao sistema era o passo 1 de vários passos que tem que ser dados ainda para implementar um sistema TEF/ECF no sistema, porque apenas implementar TEF no sistema não significa que a loja já pode começar a usufruir desta forma de pagamento, pelo contrario! Após implementar o TEF tem um procedimento de homologação do sistema para verificar se seu programa pode realmente oferecer esta forma de pagamento para os lojistas e digamos que este processo é um verdadeiro pé no saco e muito trabalhoso.

Você deve levar o seu sistema, o computador que irá utilizar o TEF, as máquinas leitoras de cartão, PINPads e todo equipamento necessário para o local que a operadora de cartão de crédito pedir para você ir para homologar, diversos testes irão ser feitos no seu sistema e prepare-se para ver todos possíveis erros que seu programa irá dar, enfim prefiro nem comentar!

Eu ja fiz uma homologação VISA para um site de um cliente e não foi nem tão simples e nem tão complicado, digamos que foi bem trabalhoso homologar a VISANET para o site e no final tudo ocorreu bem.
Foram dias e dias trabalhando a todo momento para fazer a minha primeira implementação do VISA no site do cliente, porém para a minha sorte a VISA enviou uma loja virtual com carrinho de compras já de modelo deles em ASP toda implementada com o sistema de cartão de crédito e isso para mim já foi uma grande ajuda! hehe…

Bom, voltando ao Delphi, mais especificamente ao código de barras, eu fiz a prova hoje e tem uma turma que ainda vai fazer amanhã, porém eu copiei todo o resultado da prova para o meu PENDrive e vou liberar para download aqui, vai que alguem pega uma prova parecida ou igual a minha?!..
O conteúdo que foi abordado na prova foi relativamente simples para quem decorou ou pelo menos sabe fazer o algoritmo do código de barras.

Foram dois exercícios valendo 3.5 cada, TODOS exigindo criação de componentes para realizar alguma função e todos usando property.
Nenhum exercício de criação de template, nenhum exercício com interação ao banco de dados ou algo parecido, apenas criação de componentes.

O código de verificação do DV é este: (sem nenhuma novidade do que foi passado em aula)

  1.  
  2. function TValidaCodigoBarras.ValidarCodigo(Codigo: String): char;
  3. var
  4. somapar,somaimpar,total,digito,x:integer;
  5. resultado: String;
  6.  
  7. begin
  8. somapar := 0; somaimpar := 0;
  9. total := 0; digito := 0;
  10. for x := 2 to Length(Codigo) do
  11. begin
  12.  
  13. if x mod 2 = 0 then
  14. somapar := somapar + strtoint( Codigo[Length(Codigo)+1-x] )
  15. else
  16. somaimpar := somaimpar + strtoint( Codigo[Length(Codigo)+1-x] );
  17. end;
  18.  
  19. somapar := somapar * 3;
  20. total := somapar + somaimpar;
  21. digito := (10(total mod 10)) mod 10;
  22. resultado := inttostr(digito);
  23. ValidarCodigo := resultado[1];
  24. end;
  25.  

O primeiro exercicio era para criar um componente do tipo TBitButton que ao ser clicado fizesse a leitura do Caption e informasse o número de vogais da palavra toda, por exemplo a palavra “cachorro”, temos 1 letra A, 2 letras O…
Para quem tem noção de trabalhar com Strings , estava bem simples o exercicio.
Já o exercicio 2, era para criar um componente para fazer a leitura de um código de barras de uso interno do tipo: 2 034 003 00500 5 , sendo que 034 é o código do produto, 003 a quantidade comprada e 00500 o valor do produto (5 reais), e o ultimo digito o famoso DV. Este foi um exercício também sem novidades, já que deve ser feita a verificação se o código é um código de barras válido e partir disso recuperar do código os dados requisitados.

Para extração dos dados, muitos fizeram usando laços do tipo FOR, porém era apenas necessário usar a função COPY do Pascal. Para tudo que era pedido, fiz procedimentos para cada requisito ou funções específicas, nenhuma gambiarra na programação (pelo menos acho eu! :) )

  1.  
  2. procedure TValidaCodigoBarras.ExtrairDados(Codigo: String);
  3. begin
  4. //Verificando se o valor digitado corresponde a EAN13
  5. if Length(Text) = 13 then
  6. begin
  7. //Verificando se o codigo de barras é realmente válido
  8. //baseado no seu ultimo digito (verificador);
  9. if Text[13] = ValidarCodigo(Codigo) then
  10. begin
  11. //Codigo Validado -> Extrair Dados do Código
  12. //Ex do Codigo : 2 034 003 00500 [3] (5 é o DV correto!)
  13. fCodProd := copy(codigo,2,3);
  14. fQuantidade := copy(Codigo,5,3);
  15. fValor := copy(Codigo,8,5);
  16. //Valores Atribuidos e guardados nas propriedades do componente
  17. ExibirDados;
  18. //Procedimento chamado apenas para testes
  19. //Será exibido os dados pedidos em um dialog showmessage
  20. end
  21. else
  22. showmessage(‘Atenção: O código de barras digitado não é valido!’ +#13 +
  23. ‘O código verificador correto é: ‘ + ValidarCodigo(Text));
  24. end
  25. else
  26. showmessage(‘Atenção: O código digitado não é um EAN13′);
  27.  
  28. end;
  29.  

No caso, ficou faltando apenas o procedimento que exibe os dados dos produtos, que é este: (bem simples também)

  1.  
  2. procedure TValidaCodigoBarras.ExibirDados;
  3. var
  4. valor : real;
  5. begin
  6. valor := strtofloat(fValor);
  7. valor := valor / 100;
  8.  
  9. showmessage(‘Dados do Produto:’+#13+
  10. ‘—————–’+#13+
  11. ‘Codigo: ‘      + fCodProd + #13 +
  12. ‘Quantidade: ‘  + fQuantidade + #13 +
  13. ‘Valor: ‘    + FormatCurr(‘R$ ##.##’,valor));
  14. end;
  15.  

Quem quiser dar uma olhada em todo o fonte da prova, está disponível para Download o código fonte dela aqui. (Download)
Eu acredito que todos meus exercícios estão corretos (já que todos funcionaram), mas agora o que será dito semana que vem, eu não sei! :)

Até a próxima!

Primeiro Post no Blog!

Bom, este é o primeiro de muitos posts que vou fazer aqui neste Blog.

Blog está ai para isso, expor idéias, ainda mais que resolvi desenterrar o meu site denovo e voltar a colocar no ar o luizbossoi.com.br (até que enfim)!

Em breve colocarei aqui vários conteúdos interessantes ou não só para rechear o blog e não dar a cara de um site parado! :)
Até a próxima!